トランザクション分離レベル(Transaction Isolation Level)
1. 概要
A. 定義
同時に実行される複数のトランザクションが互いにどの程度まで影響を及ぼすことを許容するか(分離の度合い) を規定したレベル。ACIDの分離性(Isolation) を実務的に実装するものであり、並行性と一貫性の間のトレードオフを調整するダイヤルである。
理想的には、すべてのトランザクションがあたかも単独で実行されているかのように完全に分離されるべきであるが(Serializable)、そうすると並行処理のスループットが急落する。そこで標準(ANSI SQL)は、完全な分離の代わりに一部の異常現象を受け入れる代償として並行性を得る複数の段階を定義した。分離レベルを理解するということは、すなわち「どの異常現象を許容し、どの性能を買うのか」を判断することである。
B. 登場背景および必要性
分離を強化すれば一貫性は高まるがロック競合が増えて並行性が低下し、緩和すれば並行性は高まるが異常現象のリスクが大きくなる。すなわち分離↑ = 一貫性↑・並行性↓ という反比例の関係が存在する。すべての業務に最高の分離を強制すれば性能が出ず、逆に低い分離を無分別に使えばデータの整合性が崩れる。したがって業務の性格 — 金銭がやり取りされ整合性が絶対的なのか、統計参照で多少の不正確さを許容してよいのか — に合わせて適正なレベルを選択することが必要である。
2. 分離レベル別の異常現象
分離レベルは、三つの代表的な異常現象を低いレベルから一つずつ遮断していく形で階段状になっている。下に行くほど多くの異常現象を防ぐ代わりに、並行性は低くなる。
| 分離レベル | Dirty Read | Non-repeatable Read | Phantom Read |
|---|---|---|---|
| Read Uncommitted | 発生 | 発生 | 発生 |
| Read Committed | 防止 | 発生 | 発生 |
| Repeatable Read | 防止 | 防止 | 発生(可能性あり) |
| Serializable | 防止 | 防止 | 防止 |
flowchart LR
A[Read Uncommitted<br/>分離 最低] --> B[Read Committed] --> C[Repeatable Read] --> D[Serializable<br/>分離 最高]
3. 異常現象の事例
三つの異常現象は、それぞれ「未確定のデータを読んだか / 同じものを二度読んだときに値が変わったか / 件数が変わったか」 によって区別される。事例で見ると原理が明確になる。
Dirty Read(ダーティリード) は、まだコミットされていない変更を読むことである。Aが残高を100→200に修正(未コミット)したものをBが200として参照し、その後Aがロールバックすると、Bは存在したことのない値を読んだことになる。Read Committed以上ではコミット済みのデータのみを読むことで、これを防ぐ。
Non-repeatable Read(反復不能読み取り) は、一つのトランザクション内で同じ行を二度読んだのに値が異なることである。Bがある行を読んで再び読む間に、Aがその行を変更・コミットすると、Bは同じ問い合わせに対して異なる答えを得る。Repeatable Readはトランザクションの間、読み取った行のスナップショットを維持してこれを防ぐ。
Phantom Read(ファントムリード) は、範囲検索の件数が変わることである。Bが「残高100以上の口座」を検索して再度検索する間に、Aが条件に合う新しい行を挿入・コミットすると、存在しなかった行が現れる。これは個別の行ではなく範囲に関するものであるため、Serializable(または範囲ロック/Next-key Lock)で完全に防がれる。
| レベル | 事例 |
|---|---|
| Read Uncommitted | Aが残高100→200を未コミット、Bが200を参照 → Aのロールバック時にDirty Read |
| Read Committed | Bの参照中にAが変更・コミット → 再参照時に値が異なる(Non-repeatable) |
| Repeatable Read | 同じ行は一貫するが、範囲検索中に新しい行が挿入されるとPhantom |
| Serializable | 完全な直列化、すべての異常現象を防止、並行性は最低 |
4. 同時実行制御の技法
分離レベルはどのように実装するかによって性能が大きく変わる。伝統的なLockベースは読み取り・書き込みに共有・排他ロックをかけ、2相ロッキング(2PL)で直列化可能性を保証するが、読み取りが書き込みを妨げるため競合が大きい。この限界を乗り越えるため、現代のDBMSはMVCC(多版型同時実行制御) を用いる。
| 技法 | 内容 |
|---|---|
| Lockベース | 共有・排他ロック、2PL(2相ロッキング) |
| MVCC | スナップショットのバージョンで読み取り-書き込みの競合を最小化(Oracle・PostgreSQL) |
| Snapshot Isolation | トランザクション開始時点のスナップショットを読んで一貫性を確保 |
MVCCが重要な理由は、データを上書きせずに複数のバージョンとして保持し、読む側は自分の時点のスナップショットを見て、書く側は新しいバージョンを作るようにすることで、「読み取りが書き込みを妨げない(readers don't block writers)」 を実現するからである。これにより、高い一貫性と高い並行性を同時に得ることができる。
5. 考慮事項および示唆
実務で注意すべき点は、DBMSごとにデフォルト値と実際の動作が異なることである。Oracle・PostgreSQLのデフォルトはRead Committedであり、MySQL InnoDBはRepeatable Readである。特にInnoDBはNext-key LockによってRepeatable ReadでもPhantomをかなりの程度防ぐ。したがって標準の理論表だけを信じるのではなく、使用するエンジンの実際の実装を確認しなければならない。選択戦略は明確である。金融決済のように整合性が絶対的であればSerializable(または明示的ロック)を、参照・統計中心であれば低いレベルで並行性を活かす。分離レベルを下げた場合は、アプリケーションレベルで楽観的ロック(バージョン列の検証) などによって整合性を補完し、性能と一貫性のバランスをとることが技術士としての判断である。
一言まとめ: 分離レベルはRead Uncommitted→Committed→Repeatable Read→Serializable と進むにつれてDirty・Non-repeatable・Phantom Readを順に遮断するが並行性を犠牲にする。Lock・MVCCで実装し、業務の整合性要求に合わせてレベルを選び、一貫性と性能のバランスをとる。