我有一个名为 migration_logs
的键值表有 2 列键,唯一,值均为 varchar(255)。
CREATE TABLE `migration_logs` (
`key` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`value` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
UNIQUE KEY `migration_logs_key_unique` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
然后我有以下 Laravel 代码:
class MigrationLog {
...
public static function beginMigration($userId)
{
$shouldMigrate = false;
$key = "luser:$userId/migration_pushed";
\DB::beginTransaction();
if (!self::query()->where('key', '=', $key)->first()) {
self::create([
'key' => $key,
'value' => Carbon::now(),
]);
$shouldMigrate = true;
}
\DB::commit();
return $shouldMigrate;
}
}
注意:您可能会认为我没有正确捕获异常,这是事实。但我的问题是关于 MySQL 的行为,而不是关于 PHP 代码中的异常处理。
你不需要了解 Laravel 来帮助我。 beginMigration() 的作用是检查某个键是否存在,如果不存在,则为其设置一些值,所有这些都在 mysql 事务内进行。隔离级别是MySql默认的。
当两个进程使用相同的输入调用 beginMigration() 时,我希望一个进程设置 key ,另一个进程不执行任何操作。
但是,我发现一种情况,当一个进程设置 key 而另一个进程尝试设置它时,因此 SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
抛出异常。
我想知道这怎么可能?怎么了?以及如何解决?
MySQL版本:5.6.27
最佳答案
更新
事务的隔离确保在数据库事务中只能看到已提交的数据。
BEGIN
INSERT INTO mytable (a,b,c ) VALUES ('x', 'y', 'z' );
INSERT INTO mytable2 (d,e,f ) VALUES ('u', 'v', 'w' );
COMMIT
数据库的其他用户将看不到“x”、“y”、“z”和“u”、“v”、“w”或两者都看不到。还不到交易的一半。
当您在事务中查询时,您正在查询已提交的数据。因此,两个进程插入相同的数据,都会看到空间可用。
当数据库系统执行冲突提交时,它确保只有第一个事务成功,而所有其他事务都失败(通过抛出异常)。
从 php
和 laravel
文档来看,commit
的预期行为似乎是一个异常(exception)。
显示的代码会将 $shouldMigrate
设置为 true,但失败时会引发异常。
如果 $shouldMigrate
的作用域位于函数之外,那么这将导致失败。此外,可能还有一个 try
/catch
block 正在阻止进程中止。 catch 的处理可能不会传播错误。
try {
\DB::commit();
} catch (Exception $e) {
echo 'Exception', $e->getMessage(), "\n";
$shouldMigrate = false;
}
这将是我能想到的最简单的修复。
关于MySQL 事务未根据完整性约束违规异常进行隔离,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34490447/