はじめに
この記事では、IBM Db2のMVCCの内部実装を深掘りします。
シリーズ最終回です。Db2のMVCCは比較的新しく(Db2 9.7〜)、他のDBの知見を取り入れた設計になっています。Db2 11.5を中心に解説します。
Db2のMVCC:Currently Committed
歴史
- Db2 9.7以前:SQL Serverと同じくロックベースの分離。読み取りが書き込みをブロック
- Db2 9.7〜:Currently Committed(CC)セマンティクスを導入
Currently Committedとは
READ COMMITTEDレベルで、ロック競合が発生したとき、ロックを待つ代わりに最後にコミットされたバージョンを返す仕組みです。
従来(Db2 9.7以前):
Tx A: UPDATE row SET salary=5000 (未コミット)
Tx B: SELECT salary FROM row → ロック待ち(Tx Aがコミットするまでブロック)
Currently Committed:
Tx A: UPDATE row SET salary=5000 (未コミット)
Tx B: SELECT salary FROM row → 3000を返す(最後にコミットされた値)
SQL ServerのRCSIと似ていますが、実装が異なります。
バージョンの保管場所
Db2はInnoDBやOracleと同じく、旧バージョンをTransaction Logから取得します。ただし、専用のUNDO領域は持ちません。
InnoDB: UNDO Tablespace(専用領域)にバージョンを保持
Oracle: UNDO Tablespace(専用領域)にバージョンを保持
SQL Server: tempdb / PVS にバージョンを保持
PostgreSQL: Heap内にバージョンを保持
Db2: Transaction Logから旧値を再構築
ログベースのバージョン取得
Db2はTransaction Logに記録されたUNDO情報を使って、必要に応じて旧バージョンを再構築します。
Data Page: [id=1, salary=5000] ← 最新版
Transaction Log:
LSN 300: UPDATE id=1, salary: 4000→5000 (UNDO: salary=4000)
LSN 200: UPDATE id=1, salary: 3000→4000 (UNDO: salary=3000)
読み取りTxがコミット済みバージョンを要求:
→ ログからUNDO情報を取得して旧値を再構築
各DBのバージョン保管比較
| InnoDB | PostgreSQL | Oracle | SQL Server | Db2 | |
|---|---|---|---|---|---|
| 保管場所 | UNDO Tablespace | Heap内 | UNDO Segment | tempdb/PVS | Transaction Log |
| 専用領域 | あり | なし | あり | あり | なし |
| 旧版の取得 | Roll Ptr辿り | xmin/xmax判定 | CR Clone | Version Store | ログから再構築 |
Lock Avoidance
Db2 9.7以降、Currently Committedに加えてLock Avoidanceという最適化があります。
仕組み
コミット済みであることが明らかな行に対して、そもそもロックを取得しません:
1. 行のログ情報を確認
2. 最後の更新トランザクションがコミット済みか判定
3. コミット済みなら → ロックなしで読み取り
4. 未コミットなら → Currently Committedで旧値を返す or ロック待ち
-- Lock Avoidanceの効果を確認
db2 get snapshot for database on mydb | grep -i "lock"
Lock waits = 15
Lock wait time (ms) = 230
Lock avoidance = 98.5% ← ほとんどロック不要分離レベル
Db2の分離レベルは独自の名称を使います:
| Db2 | 標準SQL | InnoDB | 動作 |
|---|---|---|---|
| UR(Uncommitted Read) | READ UNCOMMITTED | READ UNCOMMITTED | ダーティリード可 |
| CS(Cursor Stability) | READ COMMITTED | READ COMMITTED | Currently Committed |
| RS(Read Stability) | REPEATABLE READ | REPEATABLE READ | 読み取り行をロック |
| RR(Repeatable Read) | SERIALIZABLE | SERIALIZABLE | 範囲ロック |
デフォルトはCS(Cursor Stability)です。Currently Committedが有効な場合、CSレベルでMVCC的な動作になります。
-- Currently Committedの有効化(デフォルトON)
db2 update db cfg for mydb using CUR_COMMIT ON;旧バージョンの管理
Db2はTransaction Logから旧値を取得するため、InnoDBのPurgeやPostgreSQLのVACUUMに相当する明示的な仕組みは不要です。
ただし、ログが循環利用(Circular Logging)されると旧バージョンが失われます:
| ロギングモード | 旧バージョンの可用性 |
|---|---|
| Circular Logging | ログが上書きされると旧版が失われる |
| Archive Logging | アーカイブログがある限り旧版を取得可能 |
OracleのORA-01555と似た問題が起きる可能性がありますが、Currently Committedは直前のコミット済みバージョンだけを対象とするため、実際には問題になりにくいです。
まとめ:UNDO/MVCCシリーズ全体比較
| トピック | InnoDB | PostgreSQL | Oracle | SQL Server | Db2 |
|---|---|---|---|---|---|
| MVCC導入 | 最初から | 最初から | 最初から | 2005〜 | 9.7〜 |
| バージョン保管 | UNDO Tablespace | Heap内 | UNDO Segment | tempdb/PVS | Transaction Log |
| UPDATEの動作 | in-place | 新タプル | in-place | in-place | in-place |
| 読み取り一貫性 | ReadView | Snapshot | CR Clone | Version Store | Currently Committed |
| デフォルト動作 | MVCC | MVCC | MVCC | ロック | CC(MVCC的) |
| 旧版の削除 | Purge | VACUUM | 循環再利用 | Version Cleanup | ログの循環再利用 |
| UNDO不足時 | History List肥大化 | テーブル肥大化 | ORA-01555 | tempdb肥大化 | ログ上書き |
| SERIALIZABLE | ロック | SSI | ロック | ロック | ロック |
5つのDBを比較すると、MVCCの実装は大きく3パターンに分かれます:
- UNDO領域方式(InnoDB、Oracle):専用領域に旧バージョンを保持。テーブルは肥大化しないが、UNDO領域の管理が必要
- Heap内方式(PostgreSQL):テーブル内に全バージョンを保持。シンプルだがVACUUMが必須
- ログ/外部方式(SQL Server、Db2):Transaction Logやtempdbから旧バージョンを取得。後付け設計だが実用的
どのDBも「読み取りと書き込みを互いにブロックさせない」という同じゴールに向かって、それぞれの歴史と制約の中で異なる解を見つけています。