2009-12-21 74 views
2

考虑下面的Perl代码:MySql:事务不检测死锁?

$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(); 

我期待的是,由于更新由事务保护的,那么,如果两个进程尝试更新“编号”栏,第二个应该失败,一些错误的原因它失去了比赛。 Interbase称这为“死锁”错误。然而,MySQL会在update()调用中暂停,但在第一个调用提交之后,它将继续愉快地继续。第二个过程然后具有num的“旧”值,导致增量不正确。观察:

$ 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”。这怎么可能是正确的?

+2

这是一个InnoDB表吗? MySQL的其他存储引擎不支持事务。 – friedo 2009-12-21 20:47:45

+0

是的,我忘了提及这是一个引用InnoDB表的perl DBIx类。 – 2009-12-23 20:19:44

回答

5

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

为了让你似乎在期待的行为,你应该遵循圣路易斯的建议和执行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 Notes以及AutoCommit。当AutoCommit打开或关闭时,txn_方法的行为方式有点棘手。如果你喜欢它,我还建议阅读源代码。就我个人而言,我必须阅读源代码才能完全理解DBIC正在做什么,以便我能够获得我想要的确切行为。

+0

完美!使用for =>'update',脚本在第二个事务中获取值之前停止。我在DBIx文档中找到了“for”属性的规范:http://search.cpan.org/~frew/DBIx-Class-0.08115/lib/DBIx/Class/ResultSet.pm#for – 2009-12-23 20:24:39

+0

非常好!在我所链接的所有东西中,我无法相信我遗漏了“for”属性。对于那个很抱歉。 – bish 2009-12-23 20:45:15

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; 
+1

你也可以在你的第一个SELECT之前发出一个LOCK,这将确保第一个在下一个允许选择之前到达锁定提交。 – MindStalker 2009-12-21 20:41:50

0

这不是僵局,僵局是这样的:

TX1

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

的Tx 2个

1-更新R2 2-更新R1

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

对于你的情况,你需要锁定id = 20的行(使用select进行更新)。到达“迟到”的tx将等待一段时间(由数据库引擎定义)跟随。