MySQLに楽観的ロックを正しく実装するにはどうすればよいですか?
私たちのチームは、以下の#4を実行する必要があると推定しました。そうしないと、別のスレッドが同じバージョンのレコードを更新できるリスクがありますが、これが最善の方法であることを検証したいと考えています。
SELECT FOR UPDATE
更新しようとしているレコードを変更して、更新しようとしているレコードを誰が変更できるかをシリアル化します。明確にするために、同じバージョンのレコードを取得する同じタイムウィンドウで同じレコードを選択する2つのスレッドが、レコードを同時に更新しようとした場合に、お互いに上書きされないようにします。 #4を行わない限り、両方のスレッドが同時にそれぞれのトランザクションに入る場合(ただし、まだ更新を発行していない場合)、更新に移動すると、UPDATEを使用する2番目のスレッドが可能性があると考えています...バージョン= Xは古いデータで動作します。
バージョンフィールド/楽観的ロックを使用している場合でも、更新時にこの悲観的ロックを実行する必要があると私たちは考えていますか?
あなたの開発者は間違っています。 どちらか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)
はロックを取得して更新を続行します。ただし、バージョンは一致しなくなったため、行カウント結果で報告されるように、行は変更されません。影響を与えたのは1つのUPDATE
だけであり、クライアントアプリケーションは、どのUPDATE
が成功し、どれが失敗したかを明確に確認できます。それ以上のロックは必要ありません。
ここのPastebinのセッションログを参照 。セッション間の違いを見分けやすくするために、mysql --Prompt="A> "
などを使用しました。時間順にインターリーブされた出力をコピーして貼り付けたので、完全に生の出力ではなく、コピーと貼り付けでエラーが発生した可能性があります。自分でテストして確認してください。
notに行バージョンフィールドを追加した場合、その後確実に確実にするには、SELECT ... FOR UPDATE
を実行する必要があります。注文。
考えてみると、UPDATE
からのデータを再利用せずにSELECT
をすぐに実行している場合、または使用している場合、SELECT ... FOR UPDATE
は完全に冗長です行のバージョン管理。 UPDATE
はとにかくロックを取得します。他の誰かが読み取りと次の書き込みの間に行を更新すると、バージョンが一致しなくなるため、更新は失敗します。これが楽観的ロックの仕組みです。
SELECT ... FOR UPDATE
の目的は次のとおりです。
SERIALIZABLE
分離または行のバージョン管理を使用する必要なく、元の行に基づく新しい行を書き込みます。楽観的ロック(行のバージョン管理)とSELECT ... FOR UPDATE
の両方を使用する必要はありません。どちらかを使用してください。
UPDATE tbl SET owner = $me,
id = LAST_INSERT_ID(id)
WHERE owner = ''
LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE tbl SET owner = '' WHERE id = $id;
ロックは必要ありません(テーブルではなく、トランザクションではありません)。