【Oracle】索引構成表(IOT)完全ガイド|ORGANIZATION INDEX・主キー検索の高速化・ヒープ表との比較まで解説

「主キーでの検索が多いテーブルをさらに高速化したい」「テーブルスキャンとインデックス参照の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完全ガイドも参照してください。