根据完整性约束违规异常

时间:2015-12-28 08:58:58

标签: mysql transactions

我有一个名为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()时,我希望一个设置密钥而另一个不执行任何操作。

但是,我发现一个进程设置密钥而另一个进程尝试设置密钥的情况,因此抛出了SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry异常。

我想知道怎么可能?怎么了?以及如何解决?

MySQL版本:5.6.27

2 个答案:

答案 0 :(得分:0)

更新

事务的隔离确保在数据库事务中只能看到提交的数据。

   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'或两者。不是交易的一半。

在事务中查询时,您正在查询已提交的数据。所以插入相同数据的2个进程都会看到空间可用。

当数据库系统执行冲突提交时,它确保只有第一个事务成功,并且失败(通过抛出异常)所有其他事务。

phplaravel文档中可以看出,commit的预期行为是个例外。

显示的代码会将$shouldMigrate设置为true,但如果失败,则会抛出异常。

如果$shouldMigrate的范围超出了函数范围,则会导致失败。也可能有一个try / catch块阻止进程中止。捕获的处理可能不会传播错误。

try {
    \DB::commit();
} catch (Exception $e) {
    echo 'Exception',  $e->getMessage(), "\n";
    $shouldMigrate = false;
}

这是我能想到的最简单的修复方法。

答案 1 :(得分:0)

听起来我应该在开始执行此查询之前提高隔离级别:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

由此,我从MySQL收到此异常,而不是完整性约束违例异常:

SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction

话虽如此,对我的问题更好的解决方案是依赖于mysql错误而不是使用事务:

public static function beginMigration($userId)
{
    $key = "luser:$userId/migration_pushed";
    try {
        self::create([
            'key'   => $key,
            'value' => Carbon::now(),
        ]);
        return true;
    } catch (\Exception $e) {
        return false;
    }
}