ダーティリード、反復不能読み取り、ファントムリード
3種類の読み取り異常と、それぞれを防ぐ分離レベルを学びます。
「ダーティリード、反復不能読み取り、ファントムリード」はCoddyKit上の無料Coding Interview Prepレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはCoding Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Coding Interview Prepコースには全4レッスンが含まれています。
3つの読み取り異常
分離レベルは、読み取り異常と呼ばれる特定の同時実行バグを防ぐために存在します。面接では、3つすべてを正確に定義し、それぞれを防ぐ分離レベルと対応付けて説明することが求められます。
- ダーティーリード - コミットされていないデータを読み取ること
- 非反復読み取り - 2回の読み取りの間に行が変わること
- ファントムリード - 2回の読み取りの間に新しい行が現れること
ポイントは、非反復読み取りとファントムリードを区別することです。どちらも再度クエリを実行すると異なる結果が返るためです。
ダーティーリード:定義
ダーティーリードは、トランザクションT1が、トランザクションT2によって変更されたものの、まだコミットされていない行を読み取ると発生します。その後T2がロールバックすると、T1は実際には存在しなかったデータに基づいて処理を行ったことになります。
READ UNCOMMITTEDだけがダーティーリードを許可します。それより高い分離レベルでは、すべて禁止されます。
現実世界での危険:数秒後にロールバックされる入金を根拠にローンを承認してしまうことです。
ダーティーリード:タイムライン
2つの列をタイムラインとして読み取ってください。T1はREAD UNCOMMITTEDで実行されています。
T1には残高700が見えますが、T2は一度もコミットしません。この700は、T2の処理途中にだけ存在した幻の値です。T2がロールバックした後も、実際の値は500のままです。T1は無効なデータに基づいて判断してしまいました。
-- T2 (not committed) | -- T1 (READ UNCOMMITTED)
BEGIN; |
UPDATE accounts |
SET balance = 700 |
WHERE id = 1; |
| SELECT balance FROM accounts
| WHERE id = 1; -- reads 700 (dirty!)
ROLLBACK; |
| -- T1 acted on a value that never existed非反復読み取り:定義
非反復読み取りは、T1がある行を読み取り、T2がその同じ行を更新または削除してコミットし、T1が再度読み取ったときに異なる値を確認すると発生します。
ダーティーリードとの重要な違いに注意してください。ここではT2がコミット済みです。データ自体は実在しますが、1つのトランザクションの実行中にT1の知らないところで変化しています。
READ COMMITTEDでは、依然としてこの現象が起こり得ます。REPEATABLE READ以上では、安定したスナップショットから読み取ることで防止されます。
非反復読み取り:タイムライン
T1はREAD COMMITTEDで実行され、同じ行を2回読み取ります。その間にT2が変更をコミットします。
1つのトランザクション内で、同じ主キーから2つの異なる値が返されます。この不整合は、行が変わらないことを前提とする複数ステップのロジックを壊す可能性があります。
-- T1 (READ COMMITTED) | -- T2
BEGIN; |
SELECT balance FROM accounts |
WHERE id = 1; -- 500 |
| BEGIN;
| UPDATE accounts SET balance = 900
| WHERE id = 1;
| COMMIT;
SELECT balance FROM accounts |
WHERE id = 1; -- 900 (changed!) |
COMMIT; |ファントムリード:定義
ファントムリードは、T1が検索条件を指定したクエリを実行し、T2がその条件に一致する行をINSERT(またはDELETE)してコミットし、T1がクエリを再実行したときに異なる行の集合が返ると発生します。
非反復読み取りとの違いは、非反復読み取りが既存の行の値の変化を扱うのに対し、ファントムリードは述語に一致する行数の変化を扱う点です。
標準上、ファントムリードを防ぐことが保証されているのはSERIALIZABLEだけです。
ファントムリード:タイムライン
T1は高額口座の件数を2回数えます。その間にT2が条件に一致する新しい行を挿入してコミットします。
既存の行は何も変わっていないのに、COUNTの結果は異なります。新しい行が、T1の結果セットに現れた「ファントム」です。
-- T1 (REPEATABLE READ, standard) | -- T2
BEGIN; |
SELECT COUNT(*) FROM accounts |
WHERE balance > 1000; -- 3 |
| INSERT INTO accounts(id, balance)
| VALUES (99, 5000);
| COMMIT;
SELECT COUNT(*) FROM accounts |
WHERE balance > 1000; -- 4 (phantom)|
COMMIT; |異常と分離レベルの対応
この対応関係が、このテーマの核心です。各異常を防ぐ最低のレベルは次のとおりです。
- ダーティーリードは
READ COMMITTED以上で防止されます。 - 非反復読み取りは
REPEATABLE READ以上で防止されます。 - ファントムリードは
SERIALIZABLEで防止されます(標準に基づく場合)。
名前も対応しています。REPEATABLE READは読み取りを反復可能にし、各レベルは新たに解消する異常にちなんで名付けられています。
非反復読み取りとファントムリードの明確な違い
面接で最もよくある混同です。次の一文を覚えておいてください。
非反復読み取り = 既存の行の値が変化すること。ファントムリード = 条件に一致する行の集合が変化すること(行が追加または削除されること)。
確認してみましょう。T2がUPDATE ... WHERE id = 5を実行してコミットし、T1が5行目を再度読み取る場合、これは非反復読み取りです。T2がT1のWHERE条件に一致する新しい行に対してINSERTを実行し、T1がクエリを再実行する場合、これはファントムリードです。
ライトスキュー:もう一つの異常
上級者向けの面接では、標準的な3つの異常に加えてライトスキューについて聞かれることがあります。これは、2つのトランザクションが互いに重なり合うデータ集合を読み取り、読み取った内容に基づいて互いに重ならない書き込みを行い、両方がコミットすることで、どちらか一方だけでは許容しなかった状態を残す現象です。
典型例は、2人の医師が当直中であるケースです。それぞれが別の医師が当直中であることを確認してから、自分を勤務から外します。両方が成功すると、当直医が1人もいない状態になります。
スナップショット分離(PostgresのREPEATABLE READ)ではライトスキューが起こり得ます。これを防ぐのはSERIALIZABLEだけです。この点に触れると、理解の深さを示せます。
ロストアップデート:第4の落とし穴
面接では、ときどきロストアップデートが紛れ込んできます。これは標準の異常一覧には含まれませんが、実務では頻繁に発生します。2つのトランザクションが同じ値を読み取り、それぞれその値から新しい値を計算して書き戻すと、後の書き込みが先の書き込みを静かに上書きしてしまいます。
例として、2つの送金処理がそれぞれ残高500を読み取り、金額を差し引いて、それぞれの計算結果を書き込む場合を考えます。一方の減算が失われます。
対策は、単に分離レベルを上げることではありません。SELECT ... FOR UPDATEによる明示的なロック、またはアプリケーションではなくデータベース内で計算するアトミックな更新が必要です。
-- Safe pattern: lock the row, or compute atomically
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- locks row
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- Or simply: UPDATE accounts SET balance = balance - 100 WHERE id = 1;確認問題
動作から異常の種類を特定してください。
まとめ:異常とその解消方法
3つの読み取り異常と、それぞれをより高い分離レベルで解消する方法を確認しましょう。
- ダーティーリード(コミットされていないデータ) - READ COMMITTEDで解消されます。
- 非反復読み取り(既存の行の値が変化すること) - REPEATABLE READで解消されます。
- ファントムリード(条件に一致する行の集合が変化すること) - SERIALIZABLEで解消されます。
非反復読み取りとファントムリードの違いを明確に覚え、面接官がさらに詳しく聞いてきたらライトスキューにも触れましょう。次は、エンジンがロック、デッドロック、MVCCによって分離を実際にどのように実現するかを見ていきます。
よくある質問
「ダーティリード、反復不能読み取り、ファントムリード」レッスンは無料ですか?
はい。「ダーティリード、反復不能読み取り、ファントムリード」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Coding Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Coding Interview Prepコースには全4レッスンが含まれています。
「ダーティリード、反復不能読み取り、ファントムリード」で何を学びますか?
3種類の読み取り異常と、それぞれを防ぐ分離レベルを学びます。 ブラウザで直接実行するハンズオンコードでCoding Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Coding Interview Prepを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのCoding Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「ダーティリード、反復不能読み取り、ファントムリード」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このCoding Interview Prepレッスンでコードを書いて実行できますか?
はい。すべてのCoding Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ACID特性の解説
- 4つの分離レベル
- ダーティリード、反復不能読み取り、ファントムリード
- デッドロック、ロック、MVCC