「主キーでの検索が多いテーブルをさらに高速化したい」「テーブルスキャンとインデックス参照の2段階アクセスをなくしたい」という場面で検討するのが索引構成表(IOT: Index Organized Table)です。
IOT は B-Tree インデックスの構造にデータ行を直接格納します。通常のヒープ表のように「インデックス → ROWID → データブロック」という2段階アクセスは不要で、インデックスを辿るだけでデータが取得できます。
この記事でわかること
- IOT(索引構成表)の内部構造とヒープ表との違い
- ORGANIZATION INDEX でテーブルを作成する方法
- PCTTHRESHOLD・INCLUDING 句によるオーバーフローセグメントの管理
- IOT が効果的なユースケースと適さないケース
- 二次索引(セカンダリインデックス)と論理 ROWID の特性
- DBA_TABLES で IOT を確認する方法
ヒープ表と IOT の構造の違い
| 項目 | ヒープ表(通常のテーブル) | 索引構成表(IOT) |
|---|---|---|
| データの格納場所 | ヒープセグメント(挿入順に格納) | B-Tree インデックス構造に格納 |
| 行の物理 ROWID | 固定(ブロック番号+行番号) | 論理 ROWID(主キー値+ guess ROWID) |
| 主キー検索 | インデックス参照 → ヒープアクセス(2段階) | インデックス参照のみ(1段階) |
| 全件スキャン | ヒープを順に読む(効率的) | B-Tree の全葉ノードを読む(やや非効率) |
| PRIMARY KEY | 任意(なくても作成可能) | 必須(IOT は主キーなしでは作成不可) |
| DML 速度 | 高速(挿入は末尾に追記) | 主キー順を維持するためやや遅い |
| ストレージ | テーブルセグメント + インデックスセグメント | IOT セグメント(1 つ)のみ(主キーインデックスと兼用) |
ORGANIZATION INDEX で IOT を作成する
基本的な IOT の作成
-- 通常のヒープ表(参考: ORGANIZATION HEAP がデフォルト)
CREATE TABLE orders_heap (
order_id NUMBER NOT NULL,
customer_id NUMBER NOT NULL,
order_date DATE NOT NULL,
total_amount NUMBER(12, 2),
status VARCHAR2(20),
CONSTRAINT orders_heap_pk PRIMARY KEY (order_id)
);
-- ヒープ表: orders_heap セグメント + orders_heap_pk インデックスセグメントの2つが作成される
-- 同じ構造を IOT として作成する(ORGANIZATION INDEX を追加するだけ)
CREATE TABLE orders_iot (
order_id NUMBER NOT NULL,
customer_id NUMBER NOT NULL,
order_date DATE NOT NULL,
total_amount NUMBER(12, 2),
status VARCHAR2(20),
CONSTRAINT orders_iot_pk PRIMARY KEY (order_id)
) ORGANIZATION INDEX;
-- IOT: orders_iot の B-Tree セグメント 1 つだけ(主キーインデックスを兼ねる)
-- DBA_TABLES で確認する(IOT_TYPE 列)
SELECT table_name, iot_type, organization
FROM DBA_TABLES
WHERE owner = 'HR' AND table_name IN ('ORDERS_HEAP', 'ORDERS_IOT');
-- IOT: IOT_TYPE = 'IOT' , ORGANIZATION = 'INDEX'
-- ヒープ: IOT_TYPE = NULL, ORGANIZATION = 'HEAP'
PCTTHRESHOLD と INCLUDING 句(オーバーフローセグメント)
IOT の葉ノードは固定サイズのブロックに収まらなければなりません。列数が多いテーブルや長い列を持つテーブルでは、行のサイズがブロックに収まらない場合があります。そのような場合はオーバーフローセグメントを用意して、主キー以外の列をそこに格納します。
PCTTHRESHOLD と INCLUDING でオーバーフロー設定する
-- PCTTHRESHOLD: ブロックサイズの何% を超えた行はオーバーフローセグメントへ
-- INCLUDING: 指定した列までを IOT 本体に格納し、残りをオーバーフローへ
CREATE TABLE product_catalog_iot (
product_id NUMBER NOT NULL,
category_id NUMBER NOT NULL,
product_name VARCHAR2(200) NOT NULL,
unit_price NUMBER(10, 2),
description VARCHAR2(4000), -- 長い列はオーバーフローに格納したい
CONSTRAINT prod_catalog_pk PRIMARY KEY (product_id)
)
ORGANIZATION INDEX
PCTTHRESHOLD 20 -- ブロックの20%を超えた行はオーバーフローへ
INCLUDING product_name -- product_id と product_name までは IOT 本体に格納
OVERFLOW TABLESPACE users_tbs; -- オーバーフローセグメントの表領域
-- ポイント: 主キーでの検索頻度の高い列は IOT 本体に残す
-- → product_id(主キー)・product_name・unit_price は IOT 本体
-- description(長い列)はオーバーフローに格納される
-- オーバーフローセグメントの存在を確認する
SELECT table_name, iot_type
FROM DBA_TABLES
WHERE table_name LIKE '%PRODUCT_CATALOG%' AND owner = 'HR';
-- IOT_TYPE = 'IOT'(本体)と 'IOT_OVERFLOW'(オーバーフロー)が表示される
二次索引(セカンダリインデックス)と論理 ROWID
IOT に対して主キー以外の列でも検索できるよう、二次索引(セカンダリインデックス)を作成できます。ただし IOT の行には固定 ROWID がないため、二次索引は論理 ROWID(主キー値 + 推定物理位置)を格納します。
IOT に二次索引を作成する
-- IOT(orders_iot)に customer_id での検索用二次索引を作成する CREATE INDEX orders_iot_cust_ix ON orders_iot(customer_id); -- 二次索引は ROWID の代わりに論理 ROWID(主キー値 + guess)を格納する -- 二次索引の実行計画(参考) EXPLAIN PLAN FOR SELECT order_id, order_date FROM orders_iot WHERE customer_id = 1001; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'TYPICAL')); -- ヒープ表では: INDEX RANGE SCAN → TABLE ACCESS BY INDEX ROWID -- IOT では: INDEX RANGE SCAN(二次索引)→ INDEX UNIQUE SCAN(IOT 本体の主キー) -- 論理 ROWID の guess が外れた場合は追加の IOT ルックアップが発生する -- DBA_INDEXES で IOT の二次索引を確認する SELECT index_name, table_name, index_type FROM DBA_INDEXES WHERE table_name = 'ORDERS_IOT' AND owner = 'HR'; -- INDEX_TYPE: 'NORMAL/REV'(ヒープ表のインデックス)に対し -- IOT の二次索引は 'NORMAL'(ROWID が論理ROWIDになっている)
IOT が効果的なユースケースと適さないケース
IOT が効果的なケース
- 主キーでのポイント検索が多い:e-コマースの注文ヘッダー・コード管理テーブルなど
- 主キーの範囲検索が多い:時系列データ(order_id が連番)・ログテーブル
- ストレージを節約したい:テーブルセグメントとインデックスセグメントが統合される
- 列数が少なく行サイズが小さい:コードマスタ・設定テーブル(オーバーフローが発生しにくい)
IOT が適さないケース
- 全件スキャンが多い:B-Tree 全体を走査するためヒープ表より遅くなりやすい
- 列数が多い・行が大きい:オーバーフローセグメントへのアクセスが発生して2段階アクセスになる
- 主キーが非シーケンシャルなUUID:挿入順序がバラバラになりB-Treeの分割(スプリット)が頻発する
- 大量の UPDATE が多い:主キー以外の列を頻繁に更新する場合はヒープ表の方が効率的
まとめ
- IOT(索引構成表)はデータを B-Tree 構造に直接格納する。主キーでのポイント検索が1段階で完結するため高速
- ORGANIZATION INDEX:CREATE TABLE にこの句を加えるだけで IOT になる。PRIMARY KEY が必須
- PCTTHRESHOLD / INCLUDING:行が大きい場合はオーバーフローセグメントに一部の列を格納できる。頻繁にアクセスする列は本体側に残す
- 二次索引の論理 ROWID:主キー値+推定物理位置を格納。guess が外れた場合は追加の主キールックアップが発生する(ANALYZE でguessを更新できる)
- ユースケース:主キー検索主体の小さなマスタテーブル・コードテーブル・時系列ログが最適。全件スキャン多用や大きな行には不向き
インデックスの詳細な種類と設計については インデックス完全ガイドを参照してください。DBMS_XPLAN での実行計画確認は DBMS_XPLAN完全ガイドも参照してください。