Oracle ロック機構の内部構造を徹底解説:ITL、ロックエスカレーションなし、Enqueue まで

はじめに

この記事では、Oracle Databaseのロック機構の内部実装を深掘りします。

シリーズ3本目です。Oracleのロック設計は「読み取りは書き込みをブロックしない、書き込みは読み取りをブロックしない」を徹底しており、ロックエスカレーションが存在しないのが最大の特徴です。Oracle 19c / 23aiを中心に解説します。

Oracleのロック哲学

Oracleの設計原則:

  1. 読み取りは決してロックを取らない(CR Cloneで一貫性を保証)
  2. 書き込みは行レベルロックのみ
  3. ロックエスカレーションしない(行→テーブルへの昇格がない)
InnoDB:    SELECT ... FOR SHARE でSロック取得
PostgreSQL: SELECT ... FOR SHARE でSロック取得
Oracle:    通常のSELECTはロックなし。FOR UPDATEのみ行ロック

行ロック:ITL(Interested Transaction List)

ITLとは

Oracleの行ロックはデータブロック内のITLで管理されます。各データブロックのヘッダにITLスロットがあり、そのブロック内の行をロックしているトランザクション情報が記録されます。

Data Block Header:
- ITL Slot 1: TxID=0x000A, UBA=... ← Tx Aがこのブロック内の行をロック中
- ITL Slot 2: TxID=0x000B, UBA=... ← Tx Bもこのブロック内の行をロック中
- ITL Slot 3: (空き)
- Row 1: [lock byte → ITL Slot 1] ← Tx Aがロック
- Row 2: [lock byte → ITL Slot 2] ← Tx Bがロック
- Row 3: [lock byte = 0] ← ロックなし

各行のヘッダにある**lock byte**がITLスロットを指すことで、どのトランザクションがその行をロックしているかがわかります。

InnoDBとの違い

InnoDB Oracle
ロック情報の場所 別途ロックテーブル(lock_sys) データブロック内(ITL)
メモリ消費 ロック数に比例して増加 ブロック内のITLスロット数で固定
大量ロック時 メモリ圧迫の可能性 ITLスロット不足(ITL Wait)

ITLスロット不足

ITLスロットが足りないとITL Waitが発生します:

-- テーブル作成時にITLスロット数を指定
CREATE TABLE t1 (...) INITRANS 4 MAXTRANS 255;
-- INITRANS: 初期ITLスロット数(デフォルト1〜2)
-- MAXTRANS: 最大ITLスロット数(10g以降は常に255)

同一ブロック内の多数の行を異なるトランザクションが同時に更新する場合、INITRANSを増やすことで緩和できます。

ロックエスカレーションがない

SQL ServerやDb2では、行ロックが多くなるとテーブルロックにエスカレーションします。Oracleにはこの仕組みがありません。

SQL Server: 5000行ロック → テーブルロックにエスカレーション → 他のTxが全ブロック
Oracle:     100万行ロック → 100万行分の行ロックのまま → 他のTxは別の行にアクセス可能

これが可能なのは、ロック情報がデータブロック内(ITL)に格納されるため、ロック数が増えても別途メモリを消費しないからです。

テーブルロック(TM Lock)

OracleのテーブルロックはTM Lock(DML Lock)と呼ばれます。InnoDBのインテンションロックに相当します。

モード 略称 操作
Row Share RS (SS) SELECT … FOR UPDATE
Row Exclusive RX (SX) INSERT/UPDATE/DELETE
Share S LOCK TABLE … IN SHARE MODE
Share Row Exclusive SRX (SSX) LOCK TABLE … IN SHARE ROW EXCLUSIVE MODE
Exclusive X LOCK TABLE … IN EXCLUSIVE MODE

DML実行時は自動的にRX(Row Exclusive)のTM Lockがテーブルにかかります。これはDDL(ALTER TABLE等)との競合を防ぐためです。

Enqueue

Oracleのロック待ちはEnqueueという仕組みで管理されます。

-- ロック待ちの確認
SELECT sid, type, id1, id2, lmode, request, block
FROM v$lock WHERE block = 1 OR request > 0;

SID  TYPE  LMODE  REQUEST  BLOCK
---  ----  -----  -------  -----
100  TX    6      0        1      ← Tx 100がXロック保持、ブロック中
200  TX    0      6        0      ← Tx 200がXロック待ち

主要なEnqueueタイプ: – TX:トランザクション(行ロック) – TM:テーブル(DMLロック) – UL:ユーザー定義ロック(DBMS_LOCK

SELECT FOR UPDATEのオプション

OracleはSELECT FOR UPDATEに便利なオプションがあります:

-- NOWAIT: ロックが取れなければ即座にエラー
SELECT * FROM employees WHERE id = 1 FOR UPDATE NOWAIT;

-- WAIT N: N秒待ってダメならエラー
SELECT * FROM employees WHERE id = 1 FOR UPDATE WAIT 5;

-- SKIP LOCKED: ロック中の行をスキップ(キュー処理に便利)
SELECT * FROM jobs WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
FETCH FIRST 1 ROW ONLY;

SKIP LOCKEDはPostgreSQL 9.5、MySQL 8.0、SQL Serverでも使えるようになりましたが、Oracleが最初に実装しました。

デッドロック検出

Oracleもデッドロックを自動検出します。検出したら一方のトランザクションの現在のステートメントだけをロールバックします(トランザクション全体ではない)。

InnoDB:    デッドロック → トランザクション全体をロールバック
PostgreSQL: デッドロック → トランザクション全体をロールバック
Oracle:    デッドロック → 現在のステートメントだけロールバック(ORA-00060)

ユーザーはORA-00060を受け取った後、COMMITかROLLBACKかを選択できます。

まとめ

トピック InnoDB PostgreSQL Oracle
読み取りのロック FOR SHARE時 FOR SHARE時 なし(CR Clone)
行ロックの実装 ロックテーブル タプルヘッダ ITL(ブロック内)
ロックエスカレーション なし なし なし
Gap Lock あり なし なし
SKIP LOCKED 8.0〜 9.5〜 古くからあり
デッドロック時 Tx全体ロールバック Tx全体ロールバック ステートメントのみ

次回はSQL Server ロック機構を解説します。

参考文献

コメントする