はじめに
この記事では、Oracle Databaseのロック機構の内部実装を深掘りします。
シリーズ3本目です。Oracleのロック設計は「読み取りは書き込みをブロックしない、書き込みは読み取りをブロックしない」を徹底しており、ロックエスカレーションが存在しないのが最大の特徴です。Oracle 19c / 23aiを中心に解説します。
Oracleのロック哲学
Oracleの設計原則:
- 読み取りは決してロックを取らない(CR Cloneで一貫性を保証)
- 書き込みは行レベルロックのみ
- ロックエスカレーションしない(行→テーブルへの昇格がない)
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 ロック機構を解説します。