変更要約: 初版
4.3デッドロックと隔離性水準
デッドロックの待ちグラフによる検出と回避策、そしてREAD UNCOMMITTED・READ COMMITTED・REPEATABLE READ・SERIALIZABLEの4つの隔離性水準とダーティリード・反復不能読取り・ファントムリードの3つの読取り異常の対応関係、加えてMVCCを学び、整合性とスループットのバランスから最適な分離レベルを選ぶ判断力を養います。
在庫集計バッチと注文処理が同時に走る受発注システムを運用するDBAにとって、「どの隔離性水準を選ぶか」は、データの不整合リスクとシステム全体のスループットを両立させる最重要判断の1つです。最も厳格なSERIALIZABLEは全ての読取り異常を防ぎますが同時実行性を大きく犠牲にし、最も緩いREAD UNCOMMITTEDは高速ですがダーティリードすら許してしまいます。この節では、業務が許容できる不整合の種類を見極め、最適な隔離性水準を選ぶ判断力、およびデッドロックへの対処を学びます。
4.3.1デッドロックの検出と回避
- デッドロック=2つ以上のトランザクションが互いに相手が保持するロックの解放を待ち続け、両方とも先に進めなくなる状態。DBMSは各トランザクションの「誰が誰の解放を待っているか」を待ちグラフ(wait-for graph)として管理し、このグラフに循環(サイクル)が生じた時点でデッドロックと判定する。
- デッドロックへの対処は検出(detection)と回避(avoidance)の2方針がある。検出方式では、待ちグラフの循環を定期的にチェックし、循環を見つけたら関与するトランザクションの1つを犠牲者(victim)としてロールバックし他方を進行させる。回避方式では、ロック要求前にタイムスタンプ順などの規則(例:Wait-Die・Wound-Wait方式)で要求を許可するか即座に中断するかを判定し、そもそも循環が生じないようにする。
4.3.24つの隔離性水準と3つの読取り異常
- 3つの読取り異常:ダーティリード=他のトランザクションがまだCOMMITしていない(ロールバックされるかもしれない)変更を読んでしまう異常。反復不能読取り(Non-repeatable read)=同一トランザクション内で同じ行を2回読んだ際、間に他のトランザクションがCOMMITした更新により値が変わってしまう異常。ファントムリード=同一の検索条件で2回問い合わせた際、間に他のトランザクションが行を挿入/削除したことで行の集合(件数)が変わってしまう異常。
- 4つの隔離性水準は、この3つの異常のうちどこまで許すかで定義される:READ UNCOMMITTED(ダーティリード・反復不能読取り・ファントムリードの全てを許す)<READ COMMITTED(ダーティリードは防ぐが、反復不能読取りとファントムリードは起こり得る)<REPEATABLE READ(ダーティリードと反復不能読取りは防ぐが、ファントムリードは起こり得る)<SERIALIZABLE(3つの異常を全て防ぐ)。水準が上がるほど整合性は高まるがロック保持範囲が広がり同時実行性は下がる。
隔離性水準と読取り異常の対応表が最頻出:READ UNCOMMITTED=ダーティリード・反復不能読取り・ファントムリード全て起こり得る/READ COMMITTED=ダーティリードのみ防止(反復不能読取り・ファントムリードは起こり得る)/REPEATABLE READ=ダーティリードと反復不能読取りを防止(ファントムリードは起こり得る)/SERIALIZABLE=全て防止。「REPEATABLE READはファントムリードも防ぐ」という誤解に注意(標準的な定義ではREPEATABLE READはファントムリードを防がない)。
ある受発注システムのDBAが、次の2つの要件のバランスを検討しているとします。(1)在庫集計バッチは、集計中に他のトランザクションが在庫レコードを更新しても、バッチ開始時点の値で一貫して集計したい(同一行を複数回参照しても値が変わらないでほしい)。(2)ただし、集計対象の行が集計中に新規追加されても、システム全体のスループットを大きく落とすほど厳格な制御は避けたい。ここでDBAはまず、要件(1)が「同一行の値が読取りのたびに変わらないこと」=反復不能読取りの防止を求めていると識別します。READ COMMITTEDでは反復不能読取りは起こり得るため要件を満たさず、一方SERIALIZABLEは反復不能読取りに加えファントムリードまで防ぎますが、その分ロック範囲が広がりスループットへの影響が大きくなります。要件(2)で「新規行の増減(ファントムリード)まで厳密に防ぐ必要はない」と判断できるため、DBAはREPEATABLE READを選択します——これはダーティリードと反復不能読取りを防ぎつつ、ファントムリードは許容することでSERIALIZABLEより同時実行性を確保できる、要件に対して過不足のない選択です。ここで陥りやすい誤りは、「REPEATABLE READという名前から、読取りの再現性が完全に保証されるのでファントムリードも当然防げるはずだ」という思い込みです——REPEATABLE READは同一行の値の変化(反復不能読取り)は防ぐが、行の集合自体が増減するファントムリードは標準的な定義では防がないため、集計対象行の増減まで厳密に扱いたい場合はSERIALIZABLEまで上げるか、MVCC(多版同時実行制御・スナップショットに基づき読取りが書込みをブロックしない方式)を用いた実装の挙動を個別に確認する必要があります。
| 隔離性水準 | ダーティリード | 反復不能読取り | ファントムリード |
|---|---|---|---|
| READ UNCOMMITTED | 起こり得る | 起こり得る | 起こり得る |
| READ COMMITTED | 防止 | 起こり得る | 起こり得る |
| REPEATABLE READ | 防止 | 防止 | 起こり得る |
| SERIALIZABLE | 防止 | 防止 | 防止 |
ひっかけ: 「REPEATABLE READはその名前の通り読取りの再現性を完全に保証するのでファントムリードも防ぐ」は誤りです——標準的な定義ではREPEATABLE READが防ぐのはダーティリードと反復不能読取りまでで、ファントムリード(行の集合の増減)は防がない。ファントムリードまで防ぐにはSERIALIZABLEが必要です。また「2相ロックに従っていればデッドロックは起きない」も誤り(前節)=待ちグラフによる検出や回避方式が別途必要です。
4.3.3この節のまとめ
- デッドロックは待ちグラフの循環で検出し、犠牲者選定でロールバックするか、回避方式で事前に防ぐ
- 隔離性水準はREAD UNCOMMITTED<READ COMMITTED(ダーティリード防止)<REPEATABLE READ(+反復不能読取り防止)<SERIALIZABLE(+ファントムリード防止)の順に厳格化
- 業務が許容できる異常の種類を見極め、MVCC等も踏まえてスループットとのバランスで水準を選ぶ
進捗の記録にはログインが必要です。
理解度チェック
(軽い確認用)Q1. 在庫集計バッチが、集計中に他のトランザクションが更新しても同一行を複数回読み取った際の値が変わらないことを求めるが、集計対象行が新規追加される可能性(ファントムリード)までは厳密に防ぐ必要がないとする。スループットへの影響を抑えつつこの要件を満たす隔離性水準はどれか。
Q2. あるトランザクションが、まだCOMMITされていない他のトランザクションの変更値を読み取ってしまい、その後元のトランザクションがROLLBACKされたため誤った値に基づいて処理を続けてしまった。この現象と、これを防ぐために最低限必要な隔離性水準の組み合わせとして最も適切なものはどれか。
Q3. DBMSが各トランザクションの待ち関係を待ちグラフとして管理している。デッドロックが発生していると判定する条件として最も適切なものはどれか。

