Db2 MVCC の内部構造を徹底解説:Currently Committed、ログベース UNDO、Lock Avoidance まで

はじめに

この記事では、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も「読み取りと書き込みを互いにブロックさせない」という同じゴールに向かって、それぞれの歴史と制約の中で異なる解を見つけています。

参考文献

コメントする