2

考虑以下 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() 调用上暂停,但会在第一个调用提交后愉快地继续。然后第二个进程具有 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”。这怎么可能是正确的?

4

3 回答 3

5

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

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

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

对此

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

并重新运行您的测试。

如果您不熟悉 MySQL 中事务的复杂性,我强烈建议您阅读文档中有关InnoDB 事务模型和锁定的部分。此外,如果您还没有阅读过有关事务的DBIC 使用说明AutoCommit,请务必仔细阅读。方法在打开或关闭txn_时的行为方式AutoCommit有点棘手。如果您愿意,我还建议您阅读源代码。就个人而言,我必须阅读源代码才能完全理解 DBIC 在做什么,这样我才能得到我想要的确切行为。

于 2009-12-21T23:25:32.320 回答
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;
于 2009-12-21T20:36:27.597 回答
0

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

TX1

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

发送 2

1- 更新 R2 2- 更新 R1

如果 tx1 和 tx2 同时执行,可能会发生 tx1 等待 R2 上的锁释放,而 tx2 等待 R1 上的锁。

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

于 2009-12-21T20:42:24.507 回答