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

更新はトランザクションによって保護されているため、2 つのプロセスが「num」フィールドを更新しようとすると、2 つ目のプロセスは競合に負けたため、何らかのエラーで失敗するはずです。Interbase では、これを「デッドロック」エラーと呼んでいます。ただし、MySQL は update() 呼び出しで一時停止しますが、最初の呼び出しが commit を呼び出した後は喜んで続行します。2 番目のプロセスは 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 の提案に従い、SELECT ... FOR UPDATE関心のある行をロックするために a を実行する必要があります。そのためには、次の行を変更します。

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

これはデッドロックではありません。デッドロックは次のようなものです。

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 に答える