Issue #937284 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.
parent
f791705cc1
commit
532ea17f07
core
lib/Drupal/Core/Database/Query
modules/system
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue