web-dev-qa-db-ja.com

SELECT ... FOR UPDATEを使用する場合

SELECT ... FOR UPDATEの背後にあるユースケースの理解を助けてください。

質問1SELECT ... FOR UPDATEを使用する場合の良い例は次のとおりですか?

与えられた:

  • 部屋[ID]
  • タグ[ID、名前]
  • room_tags [room_id、tag_id]
    • room_idとtag_idは外部キーです

アプリケーションは、すべての部屋とそのタグを一覧表示したいのですが、タグのない部屋と削除された部屋を区別する必要があります。 SELECT ... FOR UPDATEが使用されていない場合、次のことが起こります。

  • 最初:
    • 部屋には[id = 1]が含まれます
    • タグには[id = 1, name = 'cats']が含まれます
    • room_tagsには[room_id = 1, tag_id = 1]が含まれます
  • スレッド1:SELECT id FROM rooms;
    • returns [id = 1]
  • スレッド2:DELETE FROM room_tags WHERE room_id = 1;
  • スレッド2:DELETE FROM rooms WHERE id = 1;
  • スレッド2:[トランザクションをコミット]
  • スレッド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_COMMITTEDSELECT ... FOR UPDATEで使用すべきですか?

回答は移植可能であることが期待されます(データベース固有ではありません)。それが不可能な場合は、その理由を説明してください。

101
Gili

ルームとタグ間の一貫性を実現し、ルームが削除された後にルームが戻らないようにする唯一のポータブルな方法は、SELECT FOR UPDATEでロックすることです。

ただし、一部のシステムではロックは同時実行制御の副作用であり、FOR UPDATEを明示的に指定しなくても同じ結果が得られます。


この問題を解決するには、スレッド1がSELECT id FROM rooms FOR UPDATEする必要があります。これにより、スレッド1が完了するまで、スレッド2がroomsから削除されなくなります。あれは正しいですか?

これは、データベースシステムが使用している同時実行制御に依存します。

  • MyISAMMySQL(および他のいくつかの古いシステム)は、クエリの実行中にテーブル全体をロックします。

  • SQL ServerSELECTクエリでは、調べたレコード/ページ/テーブルに共有ロックが設定され、DMLクエリでは更新ロックが設定されます(後で排他ロックに昇格されるか、共有ロックに降格されます) 。排他ロックは共有ロックと互換性がないため、SELECTまたはDELETEクエリは別のセッションがコミットされるまでロックされます。

  • MVCCOraclePostgreSQLMySQLInnoDBなど)を使用するデータベースでは、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 READSERIALIZABLEは異なります。SERIALIZABLEモードのリーダーは、評価するレコードにネクストキーロックを設定し、同時DMLを効果的に防ぎます。それらの上に。したがって、シリアライズ可能モードではSELECT FOR UPDATEは必要ありませんが、REPEATABLE READまたはREAD COMMITEDでは必要です。

分離モードの標準では、クエリに特定の癖が見られないことを規定していますが、その方法(ロックまたはMVCCなど)を定義していないことに注意してください。

SELECT FOR UPDATEは必要ありません」と言うとき、「特定のデータベースエンジンの実装の副作用のため」を追加すべきでした。

70
Quassnoi

短い答え:

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の影響を受ける行はロックされているため、他のトランザクションが行を変更することはできません。したがって、トランザクション分離レベルはここでは違いを生じません。

また、トランザクション分離レベルはロックとは無関係であることに注意してください。異なる分離レベルを設定しても、ロックを回避して、トランザクションによってロックされている別のトランザクションの行を更新することはできません。

トランザクション分離レベルが(異なるレベルで)保証するのは、トランザクションの進行中のデータの一貫性です。

25
Colin 't Hart