MySql:事务没有检测到死锁?

Dav*_*sky 2 mysql perl transactions dbix-class

请考虑以下perl代码:

$schema->txn_begin();

my $r = $schema->resultset('test1')->find({id=>20});

my $n = $r->num;
$r->num($n+1);
print("updating for $$\n");
$r->update();

print("$$ val: ".$r->num."\n");

sleep(4);

$schema->txn_commit();
Run Code Online (Sandbox Code Playgroud)

我期望由于更新受到事务的保护,如果两个进程尝试更新"num"字段,则第二个应该失败并出现一些错误,因为它丢失了竞争.Interbase将此称为"死锁"错误.然而,MySQL会在update()调用上暂停,但在第一个调用commit之后会很乐意继续.然后第二个进程具有num的"old"值,导致增量不正确.注意:

$ perl trans.pl  & sleep 1 ; perl trans.pl 
[1] 5569
updating for 5569
5569 val: 1015
updating for 5571
5571 val: 1015
[1]+  Done                    perl trans.pl
Run Code Online (Sandbox Code Playgroud)

两种情况下的结果值都是"1015".这怎么可能是正确的?

bis*_*ish 5

假设您使用InnoDB作为存储引擎,这是我期望的行为.InnoDB的默认事务隔离级别是REPEATABLE READ.这意味着当您执行时SELECT,事务会在该特定时间获取数据库的快照.快照不会包含尚未提交的其他事务的更新数据.由于SELECT每个进程都在提交之前发生,因此它们每个都会看到数据库处于相同的状态(num = 1014).

为了获得您似乎期待的行为,您应该遵循Lluis的建议并执行一个SELECT ... FOR UPDATE锁定您感兴趣的行.为此,请更改此行

my $r = $schema->resultset('test1')->find({id=>20});
Run Code Online (Sandbox Code Playgroud)

对此

my $r = $schema->resultset('test1')->find({id=>20}, {for=>'update'});
Run Code Online (Sandbox Code Playgroud)

并重新运行您的测试.

如果您不熟悉MySQL中事务的复杂性,我强烈建议您阅读有关InnoDB事务模型和锁定的文档部分.此外,如果您还没有,请阅读有关交易的DBIC使用说明,并且也要AutoCommit非常仔细.在方式txn_方法的行为的时候AutoCommit开启或关闭,是有点棘手.如果你愿意的话,我也建议阅读消息来源.就个人而言,我必须阅读源代码才能完全理解DBIC正在做什么,以便我能够获得我想要的确切行为.