Issue by chx, tim.plunkett, Berdir, Damien Tournoud: Fixed DEADLOCK errors on MergeQuery INSERT due to InnoDB gap locking when condition in SELECT ... FOR UPDATE results in 0 rows.

8.0.x
webchick 2012-12-21 21:09:52 -08:00
parent f791705cc1
commit 532ea17f07
2 changed files with 75 additions and 70 deletions
core
lib/Drupal/Core/Database/Query
modules/system

View File

@ -11,8 +11,6 @@ use Drupal\Core\Database\Database;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\IntegrityConstraintViolationException;
use Exception;
/**
* General class for an abstracted MERGE query operation.
*
@ -405,54 +403,42 @@ class Merge extends Query implements ConditionInterface {
}
public function execute() {
// Wrap multiple queries in a transaction, if the database supports it.
$transaction = $this->connection->startTransaction();
try {
if (!count($this->condition)) {
throw new InvalidMergeQueryException(t('Invalid merge query: no conditions'));
if (!count($this->condition)) {
throw new InvalidMergeQueryException(t('Invalid merge query: no conditions'));
}
$select = $this->connection->select($this->conditionTable)
->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)
->condition($this->condition)
->forUpdate();
$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;
catch (IntegrityConstraintViolationException $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;
}
catch (IntegrityConstraintViolationException $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 self::STATUS_UPDATE;
}
}
catch (Exception $e) {
// Something really wrong happened here, bubble up the exception to the
// caller.
$transaction->rollback();
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 self::STATUS_UPDATE;
}
// Transaction commits here where $transaction looses scope.
}
}

View File

@ -1743,36 +1743,55 @@ function system_update_8006() {
db_create_table('cache_tags', $table);
}
/**
* Adds tags, checksum_invalidations, checksum_deletions to a cache table.
*
* @param string $table
* Name of the cache table.
*/
function system_update_8007_add_tag_column($table) {
if (db_table_exists($table)) {
db_add_field($table, 'tags', array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
));
db_add_field($table, 'checksum_invalidations', array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
db_add_field($table, 'checksum_deletions', array(
'description' => 'The tag deletion sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
}
}
/**
* Modifies existing cache tables, adding support for cache tags.
*/
function system_update_8007() {
// Find all potential cache tables.
$tables = db_find_tables(Database::getConnection()->prefixTables('{cache}') . '%');
$tables = array(
'cache',
'cache_bootstrap',
'cache_form',
'cache_page',
'cache_menu',
'cache_path',
'cache_filter',
'cache_image',
'cache_update',
'cache_block',
'cache_field',
);
foreach ($tables as $table) {
// Assume we have a valid cache table if there is both 'cid' and 'data'
// columns.
if (db_field_exists($table, 'cid') && db_field_exists($table, 'data') && !db_field_exists($table, 'tags')) {
db_add_field($table, 'tags', array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
));
db_add_field($table, 'checksum_invalidations', array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
db_add_field($table, 'checksum_deletions', array(
'description' => 'The tag deletion sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
}
system_update_8007_add_tag_column($table);
}
}