Issue #937284 by chx, chrisdolby, deviantintegral, Berdir, tim.plunkett | hefox: DEADLOCK errors on MergeQuery INSERT due to InnoDB gap locking when condition in SELECT ... FOR UPDATE results in 0 rows.

merge-requests/26/head
David Rothstein 2013-12-30 17:23:37 -05:00
parent ddba1d97c7
commit 70e0b16715
2 changed files with 35 additions and 45 deletions

View File

@ -1,6 +1,8 @@
Drupal 7.25, xxxx-xx-xx (development version) Drupal 7.25, xxxx-xx-xx (development version)
----------------------- -----------------------
- Fixed a bug in the database API that caused frequent deadlock errors when
running merge queries on some servers.
- Performance improvement: Prevent block rehashing from writing blocks to the - Performance improvement: Prevent block rehashing from writing blocks to the
database on every cache clear and cron run when the blocks have not changed. database on every cache clear and cron run when the blocks have not changed.
This fix results in an extra 'saved' key which is added and set to TRUE for This fix results in an extra 'saved' key which is added and set to TRUE for

View File

@ -1606,55 +1606,43 @@ class MergeQuery extends Query implements QueryConditionInterface {
} }
public function execute() { public function execute() {
// Wrap multiple queries in a transaction, if the database supports it. if (!count($this->condition)) {
$transaction = $this->connection->startTransaction(); throw new InvalidMergeQueryException(t('Invalid merge query: no conditions'));
try { }
if (!count($this->condition)) { $select = $this->connection->select($this->conditionTable)
throw new InvalidMergeQueryException(t('Invalid merge query: no conditions')); ->condition($this->condition);
$select->addExpression('1');
if (!$select->execute()->fetchField()) {
try {
$insert = $this->connection->insert($this->table)->fields($this->insertFields);
if ($this->defaultFields) {
$insert->useDefaults($this->defaultFields);
}
$insert->execute();
return self::STATUS_INSERT;
} }
$select = $this->connection->select($this->conditionTable) catch (Exception $e) {
->condition($this->condition) // The insert query failed, maybe it's because a racing insert query
->forUpdate(); // beat us in inserting the same row. Retry the select query, if it
$select->addExpression('1'); // returns a row, ignore the error and continue with the update
if (!$select->execute()->fetchField()) { // query below.
try { if (!$select->execute()->fetchField()) {
$insert = $this->connection->insert($this->table)->fields($this->insertFields); throw $e;
if ($this->defaultFields) {
$insert->useDefaults($this->defaultFields);
}
$insert->execute();
return MergeQuery::STATUS_INSERT;
} }
catch (Exception $e) {
// The insert query failed, maybe it's because a racing insert query
// beat us in inserting the same row. Retry the select query, if it
// returns a row, ignore the error and continue with the update
// query below.
if (!$select->execute()->fetchField()) {
throw $e;
}
}
}
if ($this->needsUpdate) {
$update = $this->connection->update($this->table)
->fields($this->updateFields)
->condition($this->condition);
if ($this->expressionFields) {
foreach ($this->expressionFields as $field => $data) {
$update->expression($field, $data['expression'], $data['arguments']);
}
}
$update->execute();
return MergeQuery::STATUS_UPDATE;
} }
} }
catch (Exception $e) { if ($this->needsUpdate) {
// Something really wrong happened here, bubble up the exception to the $update = $this->connection->update($this->table)
// caller. ->fields($this->updateFields)
$transaction->rollback(); ->condition($this->condition);
throw $e; if ($this->expressionFields) {
} foreach ($this->expressionFields as $field => $data) {
// Transaction commits here where $transaction looses scope. $update->expression($field, $data['expression'], $data['arguments']);
}
}
$update->execute();
return self::STATUS_UPDATE;
}
} }
} }