あなたの開発者は間違っています。両方ではなく、いずれか SELECT ... FOR UPDATE
または行のバージョン管理が必要です。
試してみてください。オープン3つのMySQLのセッション(A)
、(B)
および(C)
同じデータベースへ。
では(C)
問題:
CREATE TABLE test(
id integer PRIMARY KEY,
data varchar(255) not null,
version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;
両方(A)
で、行のバージョンをテストして設定(B)
するUPDATE
を発行し、winner
それぞれのテキストを変更して、どのセッションがどれかを確認できます。
-- In (A):
BEGIN;
UPDATE test SET data = 'winnerA',
version = version + 1
WHERE id = 1 AND version = 0;
-- in (B):
BEGIN;
UPDATE test SET data = 'winnerB',
version = version + 1
WHERE id = 1 AND version = 0;
今では(C)
、UNLOCK TABLES;
ロックを解除します。
(A)
そして、(B)
行ロックのためにレースをします。それらの1つが勝ち、ロックを取得します。もう一方はロックをブロックします。ロックを獲得した勝者は、行を変更します。(A)
勝者であると仮定すると、変更された行を確認できます(まだコミットされていないため、他のトランザクションからは見えません)SELECT * FROM test WHERE id = 1
。
今COMMIT
、勝者のセッションで、と言ってください(A)
。
(B)
ロックを取得し、更新を続行します。ただし、バージョンは一致しなくなったため、行カウント結果で報告されるように、行は変更されません。UPDATE
効果があったのは1つだけで、クライアントアプリケーションはUPDATE
、どちらが成功し、どれが失敗したかを明確に確認できます。それ以上のロックは必要ありません。
こちらのペーストビンのセッションログを参照してください。mysql --prompt="A> "
セッションの違いをわかりやすくするためなどに使用しました。時間順にインターリーブされた出力をコピーして貼り付けたので、完全に生の出力ではなく、コピーと貼り付けでエラーが発生した可能性があります。自分でテストして確認してください。
あなたがいた場合ではない行バージョンフィールドを追加し、その後、あなたはする必要があるだろうSELECT ... FOR UPDATE
確実に順序を確保できるようにします。
考えてみると、からのデータを再利用せずにをすぐに実行している場合、または行のバージョン管理を使用している場合、SELECT ... FOR UPDATE
は完全に冗長です。とにかくロックがかかります。他の誰かが読み取りと後続の書き込みの間に行を更新すると、バージョンが一致しなくなるため、更新は失敗します。これが楽観的ロックの仕組みです。UPDATE
SELECT
UPDATE
の目的SELECT ... FOR UPDATE
は次のとおりです。
- デッドロックを回避するためにロックの順序を管理する。そして
- 行からデータを読み取りたいときに行ロックのスパンを拡張するには、アプリケーションで行ロックを変更し、
SERIALIZABLE
分離や行のバージョン管理を使用せずに、元の行に基づく新しい行を書き込みます。
楽観的ロック(行のバージョン管理)との両方を使用する必要はありませんSELECT ... FOR UPDATE
。どちらかを使用してください。