「インデックスが効いていない」「テーブルが遅い」「断片化が怖い」——Oracleのインデックスは正しく管理しないとパフォーマンスに直結します。本記事では現場DBAが必ず知っておくべきインデックスの確認・作成・再構築・無効化を完全解説します。
1. Oracleインデックスの基本 {#about}
インデックスとは、テーブルの特定列に対して作成される「索引」です。フルスキャンを避けて高速に行を特定できます。
インデックスの種類:
| 種類 | 特徴 |
|---|---|
| B-treeインデックス | デフォルト。等値・範囲検索に強い |
| ユニークインデックス | 値の重複を許さない(PRIMARY KEYに自動作成) |
| 複合インデックス | 複数列をまとめたインデックス |
| ビットマップインデックス | 低カーディナリティ列向け(DWH環境) |
| 関数ベースインデックス | UPPER(name) などの式に対して作成 |
カーディナリティとは列の値の種類の多さ。「性別(男/女)」は低カーディナリティ、「社員番号」は高カーディナリティ。B-treeインデックスは高カーディナリティ列に効果的。
2. インデックスの一覧を確認するSQL {#check-index}
自分のスキーマのインデックスを確認
-- USER_INDEXESで確認(自分のスキーマ)
SELECT
INDEX_NAME,
TABLE_NAME,
INDEX_TYPE,
UNIQUENESS,
STATUS,
LAST_ANALYZED
FROM USER_INDEXES
ORDER BY TABLE_NAME, INDEX_NAME;出力例:
INDEX_NAME TABLE_NAME INDEX_TYPE UNIQUENESS STATUS LAST_ANALYZED
------------------ ------------ ---------- ---------- ------ -------------
PK_EMPLOYEES EMPLOYEES NORMAL UNIQUE VALID 2026-05-01
IDX_EMP_DEPT_ID EMPLOYEES NORMAL NONUNIQUE VALID 2026-05-01
IDX_EMP_NAME EMPLOYEES NORMAL NONUNIQUE VALID (null)- STATUS = VALID:正常
- STATUS = UNUSABLE:無効化中(クエリで使われない)
- LAST_ANALYZED が null:統計情報未収集(要注意)
インデックスの列構成を確認
-- どの列にインデックスが付いているか確認
SELECT
INDEX_NAME,
COLUMN_POSITION,
COLUMN_NAME,
DESCEND
FROM USER_IND_COLUMNS
WHERE TABLE_NAME = 'EMPLOYEES'
ORDER BY INDEX_NAME, COLUMN_POSITION;出力例:
INDEX_NAME COLUMN_POSITION COLUMN_NAME DESCEND
---------------- --------------- ------------ -------
IDX_EMP_DEPT_ID 1 DEPT_ID ASC
PK_EMPLOYEES 1 EMPLOYEE_ID ASCDBA権限で全スキーマのインデックスを確認
-- DBA_INDEXESで全スキーマを確認(DBA権限が必要)
SELECT
OWNER,
INDEX_NAME,
TABLE_NAME,
STATUS,
LAST_ANALYZED
FROM DBA_INDEXES
WHERE OWNER = 'SCOTT' -- スキーマ名を指定
ORDER BY TABLE_NAME;3. インデックスを作成する(CREATE INDEX) {#create-index}
通常のインデックス
-- 単一列インデックス
CREATE INDEX IDX_EMP_DEPT_ID
ON EMPLOYEES(DEPT_ID);
-- 複合インデックス(カーディナリティが高い列を先頭に)
CREATE INDEX IDX_EMP_DEPT_JOB
ON EMPLOYEES(DEPT_ID, JOB_ID);
-- ユニークインデックス
CREATE UNIQUE INDEX UNX_EMP_EMAIL
ON EMPLOYEES(EMAIL);関数ベースインデックス
-- UPPER関数を使った検索に対応するインデックス
CREATE INDEX IDX_EMP_NAME_UPPER
ON EMPLOYEES(UPPER(LAST_NAME));
-- このインデックスが有効になるクエリ例
SELECT * FROM EMPLOYEES WHERE UPPER(LAST_NAME) = 'SMITH';オンラインインデックス作成(ONLINE句)
-- 本番稼働中のテーブルにインデックスを追加する場合
-- ONLINE句を付けるとDMLをブロックしない(時間はかかる)
CREATE INDEX IDX_EMP_HIRE_DATE
ON EMPLOYEES(HIRE_DATE)
ONLINE;注意:ONLINE句なしで大きなテーブルにインデックスを作成すると、テーブルロックが発生してDML(INSERT/UPDATE/DELETE)が止まります。本番では必ずONLINEを付けてください。
表領域を指定してインデックスを作成
-- インデックス用テーブルスペースに格納(推奨)
CREATE INDEX IDX_EMP_DEPT_ID
ON EMPLOYEES(DEPT_ID)
TABLESPACE INDX;4. インデックスを再構築する(REBUILD) {#rebuild}
インデックスは更新を重ねると断片化します。ALTER INDEX ... REBUILD で再構築します。
基本の再構築
-- インデックスを再構築
ALTER INDEX IDX_EMP_DEPT_ID REBUILD;
-- オンラインで再構築(本番環境向け)
ALTER INDEX IDX_EMP_DEPT_ID REBUILD ONLINE;複数インデックスをまとめて再構築するスクリプト
-- テーブル単位でインデックスを一括再構築するSQLを生成
SELECT
'ALTER INDEX ' || INDEX_NAME || ' REBUILD ONLINE;'
FROM USER_INDEXES
WHERE TABLE_NAME = 'EMPLOYEES'
AND STATUS = 'VALID';生成されたSQLをコピーして実行します。
UNUSABLE状態のインデックスを再構築
-- UNUSABLE状態のインデックスを一括再構築するSQLを生成
SELECT
'ALTER INDEX ' || OWNER || '.' || INDEX_NAME || ' REBUILD ONLINE;'
FROM DBA_INDEXES
WHERE STATUS = 'UNUSABLE';5. インデックスの無効化と有効化 {#unusable}
大量データのバッチINSERT時にインデックスを無効化すると処理が大幅に速くなります。
インデックスを無効化(UNUSABLE)
-- インデックスをUSUNABLE状態にする(クエリで使われなくなる)
ALTER INDEX IDX_EMP_DEPT_ID UNUSABLE;UNUSABLE状態でDMLを実行しても、インデックスは更新されません(エラーも出ません)。
ただしSKIP_UNUSABLE_INDEXES = FALSEの場合はエラーになるので注意。
バッチ処理後にREBUILDで有効化
-- 大量INSERTが終わったらREBUILDで再構築して有効化
ALTER INDEX IDX_EMP_DEPT_ID REBUILD ONLINE;インデックス無効化を使ったバッチ処理の流れ
-- 1. インデックスを無効化
ALTER INDEX IDX_EMP_DEPT_ID UNUSABLE;
ALTER INDEX IDX_EMP_NAME UNUSABLE;
-- 2. 大量INSERTを実行(高速化)
INSERT /*+ APPEND */ INTO EMPLOYEES SELECT * FROM EMPLOYEES_STAGE;
COMMIT;
-- 3. インデックスを再構築して有効化
ALTER INDEX IDX_EMP_DEPT_ID REBUILD ONLINE;
ALTER INDEX IDX_EMP_NAME REBUILD ONLINE;6. インデックスの断片化を確認する {#fragmentation}
ANALYZE INDEX VALIDATE STRUCTUREで断片化を確認
-- 分析実行(INDEX_STATSに結果が入る)
ANALYZE INDEX IDX_EMP_DEPT_ID VALIDATE STRUCTURE;
-- 断片化率を確認
SELECT
NAME,
HEIGHT, -- B-treeの深さ(4以上は要注意)
BLOCKS, -- 使用ブロック数
LF_ROWS, -- リーフ行数(有効データ)
DEL_LF_ROWS, -- 削除済みリーフ行数
ROUND(DEL_LF_ROWS / DECODE(LF_ROWS, 0, 1, LF_ROWS) * 100, 2) AS 断片化率
FROM INDEX_STATS;出力例:
NAME HEIGHT BLOCKS LF_ROWS DEL_LF_ROWS 断片化率
---------------- ------ ------ ------- ----------- ------
IDX_EMP_DEPT_ID 3 128 50000 18000 36.00 ← 要再構築目安:
- 断片化率 20%以上 → REBUILDを検討
- HEIGHT 4以上 → REBUILDを推奨
7. インデックスを削除する(DROP INDEX) {#drop-index}
-- インデックスを削除
DROP INDEX IDX_EMP_DEPT_ID;
-- 存在する場合のみ削除(12c以降)
DROP INDEX IF EXISTS IDX_EMP_DEPT_ID;注意:PRIMARY KEY制約に紐づくインデックスはDROP INDEXでは削除できません。制約ごと削除する必要があります。
-- PRIMARY KEY制約ごと削除する場合
ALTER TABLE EMPLOYEES DROP PRIMARY KEY;
-- または
ALTER TABLE EMPLOYEES DROP CONSTRAINT PK_EMPLOYEES;8. よくあるトラブルとエラー対処 {#errors}
ORA-01452:ユニーク制約違反でインデックスが作れない
ORA-01452: 一意なインデックスを作成できません - 重複するキーが見つかりました-- 重複データを確認してから削除
SELECT EMAIL, COUNT(*)
FROM EMPLOYEES
GROUP BY EMAIL
HAVING COUNT(*) > 1;ORA-08102:インデックスのROWIDが不正
ORA-08102: インデックス・キーが見つかりませんインデックスとテーブルの整合性が壊れている状態。REBUILDが有効です。
ALTER INDEX IDX_EMP_DEPT_ID REBUILD;それでも解消しない場合はDROP → CREATE INDEXで再作成してください。
インデックスが使われない(フルスキャンになる)
実行計画を確認して原因を特定します。
EXPLAIN PLAN FOR
SELECT * FROM EMPLOYEES WHERE DEPT_ID = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);インデックスが使われない主な原因:
| 原因 | 対処 |
|---|---|
| 統計情報が古い | DBMS_STATS.GATHER_TABLE_STATSで更新 |
WHERE句に関数使用(UPPER(列名)など) | 関数ベースインデックスを作成 |
| 暗黙の型変換(文字列↔数値) | バインド変数の型を一致させる |
| テーブルが小さい(フルスキャンが速い) | そのままでOK |
| カーディナリティが低すぎる | 別のアクセス方法を検討 |
9. まとめ:インデックス管理チェックリスト {#summary}
□ USER_INDEXES で STATUS=UNUSABLE のインデックスがないか確認
□ LAST_ANALYZED が古い(または null)のインデックスの統計情報を更新
□ 断片化率 20% 以上のインデックスは REBUILD
□ HEIGHT が 4 以上になっているインデックスは REBUILD
□ 大量バッチ前は UNUSABLE → バッチ後に REBUILD の手順を徹底
□ 本番環境での REBUILD は必ず ONLINE 句を付ける
□ インデックスが多すぎるとDMLが遅くなる → 不要なインデックスは DROP関連記事:









コメント