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

时间:2009-12-21 20:27:45

标签: 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();

我期待由于更新受到事务的保护,因此如果两个进程尝试更新“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

两种情况下的结果值均为“1015”。这怎么可能是正确的?

3 个答案:

答案 0 :(得分:5)

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

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

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

到这个

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

并重新运行测试。

如果您不熟悉MySQL中交易的复杂性,我强烈建议您阅读有关InnoDB Transaction Model and Locking的文档中的部分。另外,如果您还没有,请仔细阅读有关交易的DBIC Usage NotesAutoCommit。当txn_打开或关闭时AutoCommit方法的行为方式有点棘手。如果你愿意的话,我也建议阅读消息来源。就个人而言,我必须阅读源代码才能完全理解DBIC正在做什么,以便我能够得到我想要的确切行为。

答案 1 :(得分:0)

尝试将$ r-> num存储在mysql变量而不是perl中。 对不起,我不知道Perl,但基本上你想要的是

START TRANSACTION;
SELECT num INTO @a FROM test1 where id = 20;
UPDATE test1 set num=(@a+1) WJERE id=20;
COMMIT;

答案 2 :(得分:0)

这不是死锁,死锁是这样的:

Tx1

1-更新R1 =>在R1上写锁 2-更新R2 =>在R2上写锁定

Tx 2

1-更新R2 2-更新R1

如果tx1和tx2同时执行,则可能发生tx1等待R2上的锁定空闲,并且tx2等待R1上的锁定。

在您的情况下,您需要锁定id = 20的行(使用select for update)。到达“迟到”的tx将等待一段时间(由db引擎定义)。