SELECT ... FOR UPDATE
の背後にあるユースケースの理解を助けてください。
質問1:SELECT ... FOR UPDATE
を使用する場合の良い例は次のとおりですか?
与えられた:
アプリケーションは、すべての部屋とそのタグを一覧表示したいのですが、タグのない部屋と削除された部屋を区別する必要があります。 SELECT ... FOR UPDATEが使用されていない場合、次のことが起こります。
[id = 1]
が含まれます[id = 1, name = 'cats']
が含まれます[room_id = 1, tag_id = 1]
が含まれますSELECT id FROM rooms;
returns [id = 1]
DELETE FROM room_tags WHERE room_id = 1;
DELETE FROM rooms WHERE id = 1;
SELECT tags.name FROM room_tags, tags WHERE room_tags.tag_id = 1 AND tags.id = room_tags.tag_id;
スレッド1は、ルーム1にタグがないと考えていますが、実際にはルームは削除されています。この問題を解決するには、スレッド1がSELECT id FROM rooms FOR UPDATE
する必要があります。これにより、スレッド1が完了するまで、スレッド2がrooms
から削除されなくなります。あれは正しいですか?
質問2:いつSERIALIZABLE
トランザクション分離とREAD_COMMITTED
をSELECT ... FOR UPDATE
で使用すべきですか?
回答は移植可能であることが期待されます(データベース固有ではありません)。それが不可能な場合は、その理由を説明してください。
ルームとタグ間の一貫性を実現し、ルームが削除された後にルームが戻らないようにする唯一のポータブルな方法は、SELECT FOR UPDATE
でロックすることです。
ただし、一部のシステムではロックは同時実行制御の副作用であり、FOR UPDATE
を明示的に指定しなくても同じ結果が得られます。
この問題を解決するには、スレッド1が
SELECT id FROM rooms FOR UPDATE
する必要があります。これにより、スレッド1が完了するまで、スレッド2がrooms
から削除されなくなります。あれは正しいですか?
これは、データベースシステムが使用している同時実行制御に依存します。
MyISAM
のMySQL
(および他のいくつかの古いシステム)は、クエリの実行中にテーブル全体をロックします。
SQL Server
、SELECT
クエリでは、調べたレコード/ページ/テーブルに共有ロックが設定され、DML
クエリでは更新ロックが設定されます(後で排他ロックに昇格されるか、共有ロックに降格されます) 。排他ロックは共有ロックと互換性がないため、SELECT
またはDELETE
クエリは別のセッションがコミットされるまでロックされます。
MVCC
(Oracle
、PostgreSQL
、MySQL
とInnoDB
など)を使用するデータベースでは、DML
クエリはレコードのコピーを作成します(何らかの方法で)、一般的にリーダーはライターをブロックせず、その逆も同様です。これらのデータベースでは、SELECT FOR UPDATE
が便利です。SQL Server
と同様に、別のセッションがコミットされるまでSELECT
またはDELETE
クエリのいずれかをロックします。
いつ
REPEATABLE_READ
トランザクション分離とREAD_COMMITTED
を併用するSELECT ... FOR UPDATE
を使用する必要がありますか?
通常、REPEATABLE READ
は、幻の行(変更されるのではなく、別のトランザクションで表示または非表示になった行)を禁止しません。
Oracle
以前のPostgreSQL
バージョンでは、REPEATABLE READ
は実際にはSERIALIZABLE
の同義語です。基本的に、これはトランザクションが開始後に行われた変更を見ないことを意味します。したがって、この設定では、最後のThread 1
クエリは、削除されたことがないかのように部屋を返します(これはあなたが望んでいたものでもそうでないかもしれません)。部屋を削除した後に部屋を表示したくない場合は、SELECT FOR UPDATE
で行をロックする必要があります
InnoDB
では、REPEATABLE READ
とSERIALIZABLE
は異なります。SERIALIZABLE
モードのリーダーは、評価するレコードにネクストキーロックを設定し、同時DML
を効果的に防ぎます。それらの上に。したがって、シリアライズ可能モードではSELECT FOR UPDATE
は必要ありませんが、REPEATABLE READ
またはREAD COMMITED
では必要です。
分離モードの標準では、クエリに特定の癖が見られないことを規定していますが、その方法(ロックまたはMVCC
など)を定義していないことに注意してください。
「SELECT FOR UPDATE
は必要ありません」と言うとき、「特定のデータベースエンジンの実装の副作用のため」を追加すべきでした。
短い答え:
Q1:はい。
Q2:どちらを使用してもかまいません。
長い答え:
select ... for update
は(ある意味)特定の行を選択しますが、現在のトランザクションによってすでに更新されているかのように(またはIDの更新が実行されたかのように)ロックします。これにより、現在のトランザクションで再度更新してからコミットできます他のトランザクションがこれらの行を変更することなく
別の見方をすれば、あたかも次の2つのステートメントがアトミックに実行されるかのようです。
select * from my_table where my_condition;
update my_table set my_column = my_column where my_condition;
my_condition
の影響を受ける行はロックされているため、他のトランザクションが行を変更することはできません。したがって、トランザクション分離レベルはここでは違いを生じません。
また、トランザクション分離レベルはロックとは無関係であることに注意してください。異なる分離レベルを設定しても、ロックを回避して、トランザクションによってロックされている別のトランザクションの行を更新することはできません。
トランザクション分離レベルが(異なるレベルで)保証するのは、トランザクションの進行中のデータの一貫性です。