【Oracle】インデックスの作成・再構築・削除完全ガイド|種類・設計判断・断片化対策・ONLINE操作まで解説

【Oracle】インデックスの作成・再構築・削除完全ガイド|種類・設計判断・断片化対策・ONLINE操作まで解説 Oracle

適切なインデックスを設計するだけで、クエリが数秒から数十ミリ秒に改善することがあります。一方で、インデックスを闇雲に増やすと INSERT / UPDATE / DELETE が遅くなり、逆効果になります。

また、長期運用でインデックスが断片化すると、せっかく作ったインデックスの効果が薄れます。ALTER TABLE … MOVE の後にインデックスが全滅することも、知らないと本番で障害になります。

この記事では、Oracle のインデックスの種類と使い分けから、CREATE INDEX の全オプション断片化の確認と再構築タイミングREBUILD と COALESCE の違いまで、実務で必要な知識を体系的に解説します。

この記事でわかること
・Bツリー / UNIQUE / 複合 / 関数ベース / ビットマップ / 逆キー / 不可視インデックスの違いと使い分け
・CREATE INDEX の全オプション(ONLINE / COMPRESS / TABLESPACE / PARALLEL)
・インデックスを作るべき列・作るべきでない列の判断基準
・REBUILD と COALESCE の違いと使い分け
・断片化の確認方法と再構築タイミングの目安
・本番稼働中のオンライン作成・再構築(ONLINE オプション)
・DROP INDEX と注意点
スポンサーリンク

Oracle インデックスの種類と使い分け

Oracle には複数のインデックス種類があります。用途に合わせて選択することがパフォーマンス最適化の鍵です。

種類 特徴 主な用途
Bツリーインデックス(デフォルト) バランスツリー構造。高カーディナリティ列に最適 主キー・外部キー・WHERE 条件でよく使う列
UNIQUEインデックス 一意性を保証するBツリー。PRIMARY KEY も内部的にこれ 一意制約を持つ列(メールアドレス・社員番号など)
複合インデックス(結合インデックス) 複数列にまたがるBツリー。先頭列が重要 複数列を組み合わせた WHERE / ORDER BY
関数ベースインデックス(FBI) 列に関数を適用した結果でインデックスを作成 UPPER(name) や計算式を WHERE で使う場合
ビットマップインデックス ビット列でデータ位置を管理。低カーディナリティ列向け DWH/集計系。性別・ステータス・区分コードなど
逆キーインデックス キー値を反転して格納。RAC のホットブロック分散向け RAC 環境で連番が集中する場合
不可視インデックス(12c以降) オプティマイザに使われないが実体は存在する インデックス効果をテストしてから本番適用する際
ビットマップインデックスは OLTP には使わない
ビットマップインデックスは行レベルでなくビットマップ全体をロックするため、同時 DML が多い OLTP 環境では深刻なロック競合が発生します。DWH(データウェアハウス)や参照専用テーブルにのみ使用してください。

CREATE INDEX:インデックスの作成

Bツリーインデックス(標準)

SQL(基本構文)
CREATE [UNIQUE] INDEX インデックス名
    ON テーブル名 (列名1 [ASC|DESC], 列名2, ...)
    [TABLESPACE 表領域名]
    [ONLINE]
    [COMPRESS n]
    [PARALLEL n | NOPARALLEL];
SQL(シンプルな作成例)
-- emp テーブルの dept_id 列にインデックスを作成
CREATE INDEX idx_emp_dept
    ON emp (dept_id);
SQL(UNIQUE インデックス)
-- メールアドレスに一意インデックス
CREATE UNIQUE INDEX uq_emp_email
    ON emp (email);

複合インデックス(複数列)

複数列にまたがるインデックスです。先頭列(左端の列)が WHERE 条件に含まれていないとインデックスが使われない点が最重要ポイントです。

SQL(複合インデックス)
-- dept_id, hire_date の複合インデックス
CREATE INDEX idx_emp_dept_hire
    ON emp (dept_id, hire_date);

-- このインデックスが使われる WHERE 条件の例:
--   WHERE dept_id = 10                      ← 先頭列のみ → OK
--   WHERE dept_id = 10 AND hire_date > ...  ← 両列 → OK
--   WHERE hire_date > ...                   ← 先頭列なし → インデックス使用不可
複合インデックスの列順序の決め方
① 選択性(カーディナリティ)が高い列を先頭にする
② WHERE 条件で等値比較(=)に使う列を範囲比較(> BETWEEN)より先にする
③ ORDER BY / GROUP BY と同じ列順にすると、ソート処理が省略される場合がある

関数ベースインデックス(FBI)

列に関数を適用した結果に対してインデックスを作成します。WHERE UPPER(name) = 'YAMADA' のように関数を使う検索で効果を発揮します。

SQL(関数ベースインデックス)
-- 大文字変換した name でインデックスを作成
CREATE INDEX idx_emp_upper_name
    ON emp (UPPER(emp_name));

-- このインデックスが使われる条件:
--   WHERE UPPER(emp_name) = 'YAMADA'  ← OK
--   WHERE emp_name = 'Yamada'         ← 使われない(関数なし)
SQL(計算式のFBI)
-- 月次合計額(単価×数量)でインデックスを作成
CREATE INDEX idx_od_amount
    ON order_detail (unit_price * qty);

ビットマップインデックス(DWH向け)

SQL(ビットマップインデックス)
-- status 列(値の種類が少ない)にビットマップインデックス
CREATE BITMAP INDEX bmp_emp_status
    ON emp (status);

逆キーインデックス

キー値のバイトを反転して格納します。RAC 環境で採番した連番が特定のノードに集中するホットブロック問題を緩和します。

SQL(逆キーインデックス)
CREATE INDEX idx_emp_id_rev
    ON emp (emp_id) REVERSE;

不可視インデックス(Oracle 12c 以降)

不可視インデックスはオプティマイザに認識されないため通常のクエリでは使用されませんが、物理的には存在してデータ変更時に更新されます。本番適用前のテストに使えます。

SQL(不可視インデックス)
-- 不可視インデックスを作成
CREATE INDEX idx_emp_phone INVISIBLE
    ON emp (phone);

-- セッション内でのみ不可視インデックスを使わせる
ALTER SESSION SET OPTIMIZER_USE_INVISIBLE_INDEXES = TRUE;

-- 効果確認後、可視化して本番適用
ALTER INDEX idx_emp_phone VISIBLE;

本番稼働中のオンライン作成(ONLINE オプション)

通常の CREATE INDEX はテーブルに排他ロックをかけます。ONLINE オプションを指定すると、インデックス作成中も DML(INSERT / UPDATE / DELETE)が可能になります。

SQL(オンラインでインデックスを作成)
CREATE INDEX idx_emp_dept
    ON emp (dept_id)
    ONLINE;
オプション DML への影響 作成速度 主な用途
ONLINE なし(デフォルト) テーブルロック(DML 不可) 高速 メンテナンス時間帯・小テーブル
ONLINE あり DML 可能(ロックなし) やや低速 本番稼働中の大テーブル
PARALLEL オプションで高速作成
大テーブルのインデックス作成を速くしたい場合は PARALLEL を指定します。
CREATE INDEX idx_emp_dept ON emp (dept_id) PARALLEL 4;
作成後は ALTER INDEX idx_emp_dept NOPARALLEL; で並列度を戻すことを忘れずに。

インデックスを作るべき列・作るべきでない列

インデックスの有無はパフォーマンスに直結します。設計段階での正しい判断が重要です。

インデックスを作るべき列

条件 理由
WHERE 句で頻繁に使われる列 フルスキャンを回避できる
外部キー(FOREIGN KEY)の列 親テーブル削除時のフルスキャンを防ぐ。ロック待ちも軽減
JOIN の結合キー ネステッドループ結合で内側テーブルの検索を高速化
ORDER BY / GROUP BY でよく使う列 ソート処理を省略できる場合がある
カーディナリティが高い列 選択性が高いほどインデックスの絞り込み効果が大きい

インデックスを作るべきでない列

条件 理由
カーディナリティが低い列(性別・フラグ・区分コードなど) 絞り込み効果がなく、フルスキャンより遅くなることがある
小テーブル(数百〜数千行程度) フルスキャンのほうが速い場合がほとんど
INSERT / UPDATE / DELETE が極めて多い列 DML のたびにインデックスも更新されるためオーバーヘッドが大きい
WHERE 句でほとんど使われない列 メンテナンスコストだけかかる
既存インデックスの先頭列と同じ列 複合インデックスの先頭列は単独でも使えるため重複になる
インデックスが使われない代表的なパターン
・列に関数を適用している:WHERE SUBSTR(emp_name, 1, 3) = '山田'(FBI で対応可)
・暗黙の型変換が発生している:NUMBER 列を WHERE emp_id = '123'(文字列)で比較
IS NULL / IS NOT NULL:通常のBツリーインデックスは NULL を格納しない
LIKE '%キーワード%'(前方一致以外):先頭がワイルドカードだとインデックスを使えない
NOT IN / != / NOT BETWEEN:オプティマイザがフルスキャンを選択する場合が多い
・カーディナリティが極端に低い列:全体の10〜15%以上の行がヒットするとフルスキャンの方が速いとオプティマイザが判断する

ALTER INDEX:インデックスの再構築・メンテナンス

REBUILD と COALESCE の違い

操作 処理内容 ロック 断片化解消効果 主な用途
REBUILD インデックスを一から作り直す デフォルト:あり
ONLINE:なし
高い(完全再構築) 断片化が激しい場合・UNUSABLE を回復させる
REBUILD ONLINE DML を許可しながら再構築 なし 高い 本番稼働中の大テーブル
COALESCE 隣接するリーフブロックをマージ なし(DML 許可) 中程度(スペース再利用) 通常のメンテナンス・軽度の断片化対策
SQL(インデックスを再構築)
-- 基本の REBUILD(テーブルロックあり)
ALTER INDEX idx_emp_dept REBUILD;

-- オンライン再構築(本番稼働中も DML 可能)
ALTER INDEX idx_emp_dept REBUILD ONLINE;

-- 別の表領域に移動しながら再構築
ALTER INDEX idx_emp_dept REBUILD TABLESPACE indx2;
SQL(COALESCE:軽量な断片化解消)
-- リーフブロックをマージして断片化を解消(DML ロックなし)
ALTER INDEX idx_emp_dept COALESCE;

インデックスを無効化・有効化する

SQL(UNUSABLE で無効化・REBUILD で有効化)
-- インデックスを無効化(UNUSABLE)
-- 大量 INSERT 前に無効化してパフォーマンスを上げるテクニック
ALTER INDEX idx_emp_dept UNUSABLE;

-- 大量 INSERT 実行後に再構築して有効化
ALTER INDEX idx_emp_dept REBUILD;
大量 INSERT 前後の UNUSABLE テクニック
バッチ処理で大量のデータを INSERT するとき、事前にインデックスを UNUSABLE にすると、INSERT のたびにインデックスを更新するオーバーヘッドがなくなり大幅に高速化できます。
INSERT 完了後に REBUILD でインデックスを再構築します。ただし UNUSABLE 中はそのインデックスを使うクエリがエラーになるため、業務時間外での実施を推奨します。

インデックスの断片化を確認する

インデックスの断片化は ANALYZE INDEX … VALIDATE STRUCTUREINDEX_STATS ビューに統計情報を収集し、その後 SELECT で確認します。

SQL(断片化の確認手順)
-- ステップ1:統計を収集する
ANALYZE INDEX idx_emp_dept VALIDATE STRUCTURE;

-- ステップ2:断片化率を確認する
-- del_lf_rows_len / lf_rows_len が 20〜30% 以上なら再構築を検討
SELECT
    name,
    lf_rows,
    del_lf_rows,
    ROUND(del_lf_rows / DECODE(lf_rows, 0, 1, lf_rows) * 100, 2) AS deleted_pct,  -- DECODE でゼロ除算を防止
    height,
    blocks
FROM INDEX_STATS;
列名 意味
lf_rows リーフブロック内の有効な行数
del_lf_rows リーフブロック内の削除済み行数(再利用待ち)
deleted_pct 削除済み行の割合(再構築の目安)
height インデックスツリーの高さ(通常 2〜3。4以上は要注意)
blocks インデックスが使用しているブロック数
再構築タイミングの目安
deleted_pct(削除済み行割合)が 20〜30% 以上:COALESCE または REBUILD を検討
height(ツリーの高さ)が 4 以上:REBUILD を検討
ALTER TABLE … MOVE 実行直後:必ず全インデックスを REBUILD する
・バルクデータ削除後:削除済み行が多く残るため断片化しやすい

DROP INDEX:インデックスの削除

SQL(インデックスを削除)
DROP INDEX idx_emp_dept;
PRIMARY KEY / UNIQUE 制約のインデックスは直接 DROP できない
PRIMARY KEYUNIQUE 制約に紐づくインデックスは、DROP INDEX ではなく ALTER TABLE … DROP CONSTRAINT で制約ごと削除します。
ALTER TABLE emp DROP CONSTRAINT pk_emp;
SQL(削除前に確認:インデックスが存在するか確認)
SELECT index_name, index_type, uniqueness, status
FROM USER_INDEXES
WHERE table_name = 'EMP'
ORDER BY index_name;
インデックスの詳細情報(構成列・統計情報・DBA_INDEXES の確認など)は【Oracle】インデックス情報を取得する方法で解説しています。

インデックスの使用状況を監視する(MONITORING USAGE)

本当に使われているインデックスかどうかを確認するには MONITORING USAGE を使います。使われていないインデックスを見つけて削除することでメンテナンスコストを削減できます。

SQL(使用状況の監視を開始・確認・停止)
-- 監視開始
ALTER INDEX idx_emp_dept MONITORING USAGE;

-- 一定期間後(数日〜数週間)に使用状況を確認
SELECT index_name, table_name, monitoring, used, start_monitoring, end_monitoring
FROM V$OBJECT_USAGE
WHERE index_name = 'IDX_EMP_DEPT';

-- 監視停止
ALTER INDEX idx_emp_dept NOMONITORING USAGE;
USED = NO のインデックスは削除候補
監視期間中に一度も使われなかった(USED = NO)インデックスは削除を検討します。ただし、月次・年次バッチなど低頻度のクエリに使われている可能性があるため、監視期間は業務サイクルを考慮した十分な長さにしてください。

インデックス操作に必要な権限

操作 必要な権限
自スキーマのテーブルにインデックスを作成 なし(テーブルのオーナーなら自動的に可能)
他スキーマのテーブルにインデックスを作成 CREATE ANY INDEX システム権限
インデックスを再構築・削除する インデックスのオーナーであれば可能 / 他スキーマは ALTER ANY INDEX
インデックス作成に指定した表領域を使う その表領域の QUOTA または UNLIMITED TABLESPACE
SQL(権限付与の例:DBA が実行)
GRANT CREATE ANY INDEX TO scott;
GRANT ALTER ANY INDEX  TO scott;
権限の確認・付与方法の詳細は【Oracle】ユーザ権限を確認する方法完全ガイドを参照してください。

よくある質問

インデックスを作成したのにクエリが速くなりません
以下を確認してください。
① WHERE 句の列に関数が適用されていないか(WHERE UPPER(name) = … → FBI が必要)
② 暗黙の型変換が発生していないか(NUMBER 列を文字列で比較など)
③ 統計情報が古くないか(DBMS_STATS.GATHER_TABLE_STATS で統計を更新)
④ テーブルの行数が少なすぎないか(小テーブルはフルスキャンの方が速い)
⑤ 実行計画(EXPLAIN PLAN)でインデックスが実際に使われているか確認する
ORA-01502: インデックスまたはインデックスのパーティションが UNUSABLE 状態です
インデックスが UNUSABLE 状態になっています。主な原因は①ALTER TABLE … MOVE の実行、②パーティション操作(TRUNCATE PARTITION など)、③ALTER INDEX … UNUSABLE の実行です。
ALTER INDEX インデックス名 REBUILD; で再構築して VALID 状態に戻してください。
REBUILD と COALESCE はどちらを使うべきですか?
断片化が軽度の場合や本番稼働中のメンテナンスには COALESCE(DML ロックなし・軽量)を使います。
断片化が深刻な場合・UNUSABLE からの回復・表領域を変更したい場合は REBUILD を使います。
本番稼働中は REBUILD ONLINE を指定することで DML を止めずに再構築できます。
外部キー列にインデックスを作成しないと何か問題が起きますか?
親テーブルの行を削除したとき、Oracle は子テーブルをフルスキャンして参照整合性を確認します。外部キー列にインデックスがないと、このフルスキャンでパフォーマンスが低下するだけでなく、子テーブル全体にロックがかかり他のトランザクションがブロックされる「デッドロック的な待機」が発生することがあります。外部キー列には必ずインデックスを作成してください。
複合インデックスの列順を間違えるとどうなりますか?
複合インデックスは先頭列から順にしか使われません。
例えば (dept_id, emp_name) の順で作ったインデックスは、WHERE emp_name = '山田' だけでは使われません。先頭列の dept_id を含む条件がないとインデックスは使えないため、クエリのアクセスパターンを分析してから列順を決めることが重要です。
Oracle で IS NULL でインデックスを使いたい
通常の Bツリーインデックスは NULL 値を格納しないため、IS NULL でインデックスは使われません。
対処法は①関数ベースインデックス(NVL(列名, 0) など)を作成する、②複合インデックスに NULL でない別の列を含める(複合インデックスは構成列のいずれかが非 NULL なら NULL も格納される)、の2つです。

まとめ

Oracle のインデックス操作のポイントをまとめます。

やりたいこと 推奨する方法
標準的なインデックスを作成する CREATE INDEX … ON テーブル名 (列名)
本番稼働中に作成する CREATE INDEX … ONLINE
関数を使う WHERE 条件を高速化する 関数ベースインデックス(FBI)を作成
インデックスの効果を本番適用前にテストする INVISIBLE インデックスで検証(12c以降)
大量 INSERT を高速化する 事前に UNUSABLE → INSERT 後に REBUILD
断片化を解消する(軽量・DML許可) ALTER INDEX … COALESCE
断片化を完全解消・UNUSABLE を回復する ALTER INDEX … REBUILD [ONLINE]
使われていないインデックスを特定する MONITORING USAGE → V$OBJECT_USAGE 確認
不要なインデックスを削除する DROP INDEX(制約付きは ALTER TABLE DROP CONSTRAINT)
テーブルとインデックスを別表領域に分ける CREATE INDEX … TABLESPACE indx(I/O 分散・管理の明確化)

インデックスの詳細な情報取得(USER_INDEXES・ALL_IND_COLUMNS を使った確認 SQL)については【Oracle】インデックス情報を取得する方法を、テーブルの作成については【Oracle】テーブルを作成する方法完全ガイドを参照してください。