適切なインデックスを設計するだけで、クエリが数秒から数十ミリ秒に改善することがあります。一方で、インデックスを闇雲に増やすと 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以降) | オプティマイザに使われないが実体は存在する | インデックス効果をテストしてから本番適用する際 |
ビットマップインデックスは行レベルでなくビットマップ全体をロックするため、同時 DML が多い OLTP 環境では深刻なロック競合が発生します。DWH(データウェアハウス)や参照専用テーブルにのみ使用してください。
CREATE INDEX:インデックスの作成
Bツリーインデックス(標準)
CREATE [UNIQUE] INDEX インデックス名
ON テーブル名 (列名1 [ASC|DESC], 列名2, ...)
[TABLESPACE 表領域名]
[ONLINE]
[COMPRESS n]
[PARALLEL n | NOPARALLEL];
-- emp テーブルの dept_id 列にインデックスを作成
CREATE INDEX idx_emp_dept
ON emp (dept_id);
-- メールアドレスに一意インデックス
CREATE UNIQUE INDEX uq_emp_email
ON emp (email);
複合インデックス(複数列)
複数列にまたがるインデックスです。先頭列(左端の列)が WHERE 条件に含まれていないとインデックスが使われない点が最重要ポイントです。
-- 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' のように関数を使う検索で効果を発揮します。
-- 大文字変換した name でインデックスを作成
CREATE INDEX idx_emp_upper_name
ON emp (UPPER(emp_name));
-- このインデックスが使われる条件:
-- WHERE UPPER(emp_name) = 'YAMADA' ← OK
-- WHERE emp_name = 'Yamada' ← 使われない(関数なし)
-- 月次合計額(単価×数量)でインデックスを作成
CREATE INDEX idx_od_amount
ON order_detail (unit_price * qty);
ビットマップインデックス(DWH向け)
-- status 列(値の種類が少ない)にビットマップインデックス
CREATE BITMAP INDEX bmp_emp_status
ON emp (status);
逆キーインデックス
キー値のバイトを反転して格納します。RAC 環境で採番した連番が特定のノードに集中するホットブロック問題を緩和します。
CREATE INDEX idx_emp_id_rev
ON emp (emp_id) REVERSE;
不可視インデックス(Oracle 12c 以降)
不可視インデックスはオプティマイザに認識されないため通常のクエリでは使用されませんが、物理的には存在してデータ変更時に更新されます。本番適用前のテストに使えます。
-- 不可視インデックスを作成
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)が可能になります。
CREATE INDEX idx_emp_dept
ON emp (dept_id)
ONLINE;
| オプション | DML への影響 | 作成速度 | 主な用途 |
|---|---|---|---|
| ONLINE なし(デフォルト) | テーブルロック(DML 不可) | 高速 | メンテナンス時間帯・小テーブル |
| ONLINE あり | DML 可能(ロックなし) | やや低速 | 本番稼働中の大テーブル |
大テーブルのインデックス作成を速くしたい場合は
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 許可) | 中程度(スペース再利用) | 通常のメンテナンス・軽度の断片化対策 |
-- 基本の REBUILD(テーブルロックあり) ALTER INDEX idx_emp_dept REBUILD; -- オンライン再構築(本番稼働中も DML 可能) ALTER INDEX idx_emp_dept REBUILD ONLINE; -- 別の表領域に移動しながら再構築 ALTER INDEX idx_emp_dept REBUILD TABLESPACE indx2;
-- リーフブロックをマージして断片化を解消(DML ロックなし) ALTER INDEX idx_emp_dept COALESCE;
インデックスを無効化・有効化する
-- インデックスを無効化(UNUSABLE) -- 大量 INSERT 前に無効化してパフォーマンスを上げるテクニック ALTER INDEX idx_emp_dept UNUSABLE; -- 大量 INSERT 実行後に再構築して有効化 ALTER INDEX idx_emp_dept REBUILD;
バッチ処理で大量のデータを INSERT するとき、事前にインデックスを UNUSABLE にすると、INSERT のたびにインデックスを更新するオーバーヘッドがなくなり大幅に高速化できます。
INSERT 完了後に
REBUILD でインデックスを再構築します。ただし UNUSABLE 中はそのインデックスを使うクエリがエラーになるため、業務時間外での実施を推奨します。インデックスの断片化を確認する
インデックスの断片化は ANALYZE INDEX … VALIDATE STRUCTURE で INDEX_STATS ビューに統計情報を収集し、その後 SELECT で確認します。
-- ステップ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:インデックスの削除
DROP INDEX idx_emp_dept;
PRIMARY KEY や UNIQUE 制約に紐づくインデックスは、DROP INDEX ではなく ALTER TABLE … DROP CONSTRAINT で制約ごと削除します。ALTER TABLE emp DROP CONSTRAINT pk_emp;SELECT index_name, index_type, uniqueness, status FROM USER_INDEXES WHERE table_name = 'EMP' ORDER BY index_name;
インデックスの使用状況を監視する(MONITORING USAGE)
本当に使われているインデックスかどうかを確認するには MONITORING USAGE を使います。使われていないインデックスを見つけて削除することでメンテナンスコストを削減できます。
-- 監視開始 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)インデックスは削除を検討します。ただし、月次・年次バッチなど低頻度のクエリに使われている可能性があるため、監視期間は業務サイクルを考慮した十分な長さにしてください。インデックス操作に必要な権限
| 操作 | 必要な権限 |
|---|---|
| 自スキーマのテーブルにインデックスを作成 | なし(テーブルのオーナーなら自動的に可能) |
| 他スキーマのテーブルにインデックスを作成 | CREATE ANY INDEX システム権限 |
| インデックスを再構築・削除する | インデックスのオーナーであれば可能 / 他スキーマは ALTER ANY INDEX |
| インデックス作成に指定した表領域を使う | その表領域の QUOTA または UNLIMITED TABLESPACE |
GRANT CREATE ANY INDEX TO scott; GRANT ALTER ANY INDEX TO scott;
よくある質問
① WHERE 句の列に関数が適用されていないか(
WHERE UPPER(name) = … → FBI が必要)② 暗黙の型変換が発生していないか(NUMBER 列を文字列で比較など)
③ 統計情報が古くないか(
DBMS_STATS.GATHER_TABLE_STATS で統計を更新)④ テーブルの行数が少なすぎないか(小テーブルはフルスキャンの方が速い)
⑤ 実行計画(EXPLAIN PLAN)でインデックスが実際に使われているか確認する
ALTER TABLE … MOVE の実行、②パーティション操作(TRUNCATE PARTITION など)、③ALTER INDEX … UNUSABLE の実行です。ALTER INDEX インデックス名 REBUILD; で再構築して VALID 状態に戻してください。COALESCE(DML ロックなし・軽量)を使います。断片化が深刻な場合・UNUSABLE からの回復・表領域を変更したい場合は
REBUILD を使います。本番稼働中は
REBUILD ONLINE を指定することで DML を止めずに再構築できます。例えば
(dept_id, emp_name) の順で作ったインデックスは、WHERE emp_name = '山田' だけでは使われません。先頭列の dept_id を含む条件がないとインデックスは使えないため、クエリのアクセスパターンを分析してから列順を決めることが重要です。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】テーブルを作成する方法完全ガイドを参照してください。

