はじめに
この記事では、Oracle Databaseのインデックスの内部実装を深掘りします。
InnoDB、PostgreSQLに続く3本目です。OracleはBitmap IndexやIndex-Organized Table(IOT)など、独自のインデックス機能が豊富です。Oracle 19c / 23aiを中心に、以下を解説します:
- Heap TableとIndex(PostgreSQLと同じ分離構造)
- B-tree Indexの内部構造
- Index-Organized Table(IOT):InnoDBのClustered Indexに相当
- Bitmap Index:DWH向けの特殊インデックス
- その他のインデックスタイプ
Heap TableとIndex
OracleのテーブルはPostgreSQLと同じくHeap構造がデフォルトです。レコードは挿入順に格納され、インデックスとは分離しています。
Oracle用語 InnoDB用語 PostgreSQL用語
──────────────────────────────────────────────────────────
Heap Table Clustered Index Heap
ROWID ― ctid
B-tree Index Secondary Index B-tree Index
IOT Clustered Index ―
ROWID
PostgreSQLのctidに相当するのがROWIDです。行の物理位置を表します:
ROWID = OOOOOOFFFBBBBBBRRR
^^^^^^ データオブジェクト番号
^^^ 相対ファイル番号
^^^^^^ ブロック番号
^^^ 行番号
SELECT ROWID, employee_id, last_name FROM employees WHERE employee_id = 100;
ROWID EMPLOYEE_ID LAST_NAME
------------------ ----------- ---------
AAAEAeAAEAAAADNAAA 100 Kingインデックスのリーフにはキー値とROWIDが格納されます。PostgreSQLのctidと同じ仕組みです。
B-tree Index
OracleのB-tree IndexはPostgreSQLやInnoDBと基本構造は同じですが、いくつかの特徴があります。
ブランチブロックとリーフブロック
[Branch Block]
| Smith | Jones |
/ | \
[Leaf Block] [Leaf Block] [Leaf Block]
Adams,ROWID Jones,ROWID Smith,ROWID
Brown,ROWID King,ROWID Taylor,ROWID
↔ ↔ ↔
(リーフ同士が双方向リンクリストで接続)
NULLの扱い
OracleのB-tree Indexは全キーカラムがNULLの行をインデックスに含めません。これは重要な特徴です:
-- この検索はインデックスを使えない
SELECT * FROM employees WHERE commission_pct IS NULL;
-- 回避策:NULLでない定数を含む複合インデックス
CREATE INDEX idx_comm ON employees(commission_pct, 0);InnoDBとPostgreSQLはNULLをインデックスに含めます。
Index Block構造
- Block Header ブロック管理情報
- Index Entry 1: [Key, ROWID]
- Index Entry 2: [Key, ROWID]
- …
- Free Space
- Row Directory 各エントリへのオフセット
TREEDUMP で内部構造を確認
ALTER SESSION SET EVENTS 'immediate trace name treedump level INDEX_OBJECT_ID';----- begin tree dump
branch: 0x1400021 (block 33)
leaf: 0x1400022 (block 34) keys 300
leaf: 0x1400023 (block 35) keys 285
leaf: 0x1400024 (block 36) keys 290
----- end tree dump
Index-Organized Table(IOT)
IOTとは
IOTはテーブルデータをB-tree Indexのリーフに格納する構造です。InnoDBのClustered Indexとほぼ同じ概念です。
CREATE TABLE sessions (
session_id NUMBER PRIMARY KEY,
user_id NUMBER,
login_time TIMESTAMP,
data VARCHAR2(200)
) ORGANIZATION INDEX; -- ← これでIOTになる通常のHeap Table + Index:
Index Leaf: [session_id=1, ROWID] → Heap Block: [session_id=1, user_id=5, ...]
(2回のI/O)
IOT:
B-tree Leaf: [session_id=1, user_id=5, login_time=..., data=...]
(1回のI/O)
IOTとInnoDBの比較
| InnoDB Clustered Index | Oracle IOT | |
|---|---|---|
| デフォルト | 常にClustered Index | Heap Table(明示的にIOT指定が必要) |
| Secondary Index | PK値を格納 | Logical ROWID(推測ベース) |
| Overflow | なし(全データがリーフ) | OVERFLOW句で大きなカラムを別セグメントに |
OracleでIOTが使われるのは、PK検索が主体のテーブル(セッション管理、ルックアップテーブル等)に限られます。InnoDBのように全テーブルがClustered Indexという設計とは対照的です。
Bitmap Index
OracleのBitmap Indexは他のDBにはない(PostgreSQLにはBRINがあるが別物)独自機能です。
Bitmap Indexとは
カーディナリティ(値の種類)が低いカラムに有効なインデックスです。各値に対してビットマップ(ビット配列)を持ちます:
gender カラムの Bitmap Index:
値 'M': 1 0 1 1 0 1 0 0 1 1 ← 各ビットが行に対応(1=該当)
値 'F': 0 1 0 0 1 0 1 1 0 0
ビットマップ演算
複数条件の組み合わせがビット演算で超高速に処理できます:
SELECT COUNT(*) FROM employees
WHERE gender = 'M' AND department = 'Sales';
-- 内部処理:
-- gender='M': 1 0 1 1 0 1 0 0 1 1
-- dept='Sales': 0 0 1 0 0 1 0 0 1 0
-- AND結果: 0 0 1 0 0 1 0 0 1 0 ← 3行が該当制約
Bitmap IndexはDWH(データウェアハウス)向けです。OLTP環境では使えません:
- 行レベルロックではなくビットマップセグメント単位のロック
- 同時更新でロック競合が激しい
- INSERT/UPDATE/DELETEが遅い
| B-tree Index | Bitmap Index | |
|---|---|---|
| カーディナリティ | 高い(ユニーク値が多い) | 低い(性別、ステータス等) |
| 複数条件の組み合わせ | Index Merge(遅い) | ビット演算(超高速) |
| 同時更新 | 行レベルロック | セグメントロック(競合大) |
| 用途 | OLTP | DWH |
まとめ
| トピック | InnoDB | PostgreSQL | Oracle |
|---|---|---|---|
| テーブル構造 | Clustered Index | Heap | Heap(IOTも選択可) |
| 主要インデックス | B+Tree | B-tree, GiST, GIN, BRIN | B-tree, Bitmap, Function-based |
| NULLの扱い | インデックスに含む | インデックスに含む | 全キーNULLは除外 |
| 並行制御 | SX-latch | Lehman-Yao | ラッチ(ITL方式) |
| DWH向け | なし | BRIN | Bitmap Index |
| Clustered構造 | 常に(デフォルト) | なし | IOT(明示指定) |
次回はSQL Server Indexを同じ切り口で解説します。