「このテーブルにインデックスが張られているか?」「複合インデックスの列の順番は正しいか?」「インデックスは本当に使われているのか?」――チューニング作業では、まずインデックスの現状を正確に把握することが出発点です。
Oracle にはインデックスに関する豊富なデータディクショナリビューが用意されています。USER_INDEXES でインデックスの基本情報と統計を確認し、USER_IND_COLUMNS で構成列を確認し、V$OBJECT_USAGE で実際に使われているかを監視する――こうした情報を組み合わせることで、不要なインデックスや問題のあるインデックスを特定できます。
この記事では、インデックス情報を取得するために使う各ビューの列の意味から、重複インデックスの検出・DDL の取得まで、実務で役立つ確認方法を体系的に解説します。
・USER_INDEXES / ALL_INDEXES / DBA_INDEXES の各列の意味と使い方
・USER_IND_COLUMNS でインデックスの構成列・順序・降順を確認する方法
・USER_IND_EXPRESSIONS でファンクションベースインデックスの式を確認する方法
・CLUSTERING_FACTOR などの統計情報の読み方
・V$OBJECT_USAGE でインデックスの使用状況を監視する手順
・INDEX_STATS(ANALYZE INDEX)でブロック使用率を確認する方法
・重複インデックスを SQL で検出する方法
・DBMS_METADATA.GET_DDL でインデックスの CREATE 文を取得する方法
・INVISIBLE インデックスの状態確認
- インデックス情報に使うビューの一覧
- USER_INDEXES:インデックスの基本情報と統計
- USER_IND_COLUMNS:インデックスの構成列を確認する
- USER_IND_EXPRESSIONS:ファンクションベースインデックスの式を確認
- V$OBJECT_USAGE:インデックスの使用状況を監視する
- INDEX_STATS:ブロック使用率と断片化を確認する
- 重複インデックスを SQL で検出する
- INVISIBLE インデックスの確認(Oracle 11g 以降)
- DBA_IND_PARTITIONS:パーティションインデックスを確認する
- DBMS_METADATA.GET_DDL:インデックスの CREATE 文を取得する
- 実践的な確認パターン集
- よくある質問
- まとめ
インデックス情報に使うビューの一覧
| ビュー名 | 参照範囲 | 主な用途 |
|---|---|---|
| USER_INDEXES | 自分のスキーマのインデックスのみ | インデックスの状態・統計を確認(DBA 権限不要) |
| ALL_INDEXES | 自分がアクセス権限を持つインデックス | 他スキーマのインデックスを参照する場合 |
| DBA_INDEXES | データベース内のすべてのインデックス | DBA がスキーマ横断でインデックスを調査する場合 |
| USER_IND_COLUMNS | 自分のスキーマの構成列情報 | インデックスの構成列・列順・降順を確認 |
| ALL_IND_COLUMNS | アクセス可能なインデックスの構成列 | 他スキーマの構成列を確認 |
| DBA_IND_COLUMNS | すべてのインデックスの構成列 | DBA がスキーマ横断で構成列を確認 |
| USER_IND_EXPRESSIONS | 自分のスキーマの FBI 式 | ファンクションベースインデックスの式を確認 |
| V$OBJECT_USAGE | 使用状況の監視結果 | インデックスが実際にクエリで使われているか確認 |
| INDEX_STATS | ANALYZE INDEX の実行結果 | インデックスのブロック使用率・断片化を確認 |
| DBA_IND_PARTITIONS | パーティションインデックスの情報 | パーティションごとのインデックス状態・統計 |
USER_INDEXES:インデックスの基本情報と統計
USER_INDEXES(DBA 権限があれば DBA_INDEXES)はインデックスのほぼすべての属性を持つ基本ビューです。各列の意味を理解することで、チューニングに必要な情報を読み取れます。
主な列の意味
| 列名 | データ型 | 説明 |
|---|---|---|
| INDEX_NAME | VARCHAR2(128) | インデックス名 |
| TABLE_NAME | VARCHAR2(128) | 所属するテーブル名 |
| TABLE_OWNER | VARCHAR2(128) | テーブルの所有者(ALL_/DBA_ ビューのみ) |
| INDEX_TYPE | VARCHAR2(27) | インデックス種別。NORMAL(B-tree)/ BITMAP / FUNCTION-BASED NORMAL / CLUSTER / IOT など |
| UNIQUENESS | VARCHAR2(9) | 一意性。UNIQUE / NONUNIQUE |
| STATUS | VARCHAR2(8) | 状態。VALID(正常)/ UNUSABLE(使用不可・再構築が必要)/ N/A(パーティションインデックスの親) |
| VISIBILITY | VARCHAR2(9) | VISIBLE(通常)/ INVISIBLE(オプティマイザから見えない・12c+) |
| NUM_ROWS | NUMBER | インデックスエントリ数(統計収集後に更新) |
| DISTINCT_KEYS | NUMBER | 一意キー値の数。選択性の評価に使う |
| CLUSTERING_FACTOR | NUMBER | テーブルの物理的な並び順とインデックスの整合度(後述) |
| LEAF_BLOCKS | NUMBER | リーフブロック数。インデックスの物理サイズの指標 |
| LAST_ANALYZED | DATE | 統計情報を最後に収集した日時 |
| BLEVEL | NUMBER | B-tree インデックスの深さ(分岐レベル数)。通常は 2〜3 が健全 |
| PCT_FREE | NUMBER | ブロック内の空き領域の割合(INSERT 用) |
| COMPRESSION | VARCHAR2(8) | インデックス圧縮の有無(ENABLED / DISABLED) |
| LOGGING | VARCHAR2(3) | REDO ログ記録の有無(YES / NO) |
| PARTITIONED | VARCHAR2(3) | パーティションインデックスかどうか(YES / NO) |
-- 特定テーブルのインデックス一覧(状態・統計含む)
SELECT index_name,
index_type,
uniqueness,
status,
visibility,
num_rows,
distinct_keys,
clustering_factor,
leaf_blocks,
blevel,
last_analyzed
FROM user_indexes
WHERE table_name = 'EMPLOYEES'
ORDER BY index_name;
-- DBA が特定スキーマのインデックスを確認
SELECT owner,
index_name,
table_name,
index_type,
uniqueness,
status,
visibility,
num_rows,
last_analyzed
FROM dba_indexes
WHERE owner = 'HR'
ORDER BY table_name, index_name;
CLUSTERING_FACTOR の読み方
CLUSTERING_FACTOR はインデックスを使ったアクセスのコストに直結する重要な統計値です。
| CLUSTERING_FACTOR の値 | 意味 | インデックスアクセスへの影響 |
|---|---|---|
| NUM_ROWS に近い | テーブルの行がインデックス順とほぼ同じ物理順で格納されている | ◎ インデックスレンジスキャンが高効率(1インデックスエントリ ≈ 1ブロック) |
| BLOCKS に近い | テーブルの行がランダムに配置されている | △ インデックスレンジスキャンが非効率(1インデックスエントリごとにブロック読み込みが発生) |
| BLOCKS より大きい | かなりランダムで断片化している可能性 | ✗ フルテーブルスキャンの方が速い場合がある |
選択性(SELECT 件数 / 全件数)と CLUSTERING_FACTOR を組み合わせてインデックス効率を評価できます。
-- CLUSTERING_FACTOR / NUM_ROWS が 1 に近いほど効率が良い
SELECT index_name,
table_name,
num_rows,
distinct_keys,
clustering_factor,
leaf_blocks,
ROUND(clustering_factor / NULLIF(num_rows, 0), 4) AS cf_ratio,
ROUND(distinct_keys / NULLIF(num_rows, 0), 4) AS selectivity
FROM user_indexes
WHERE table_name = 'ORDERS'
ORDER BY cf_ratio;
USER_IND_COLUMNS:インデックスの構成列を確認する
USER_IND_COLUMNS にはインデックスを構成する列の情報が格納されています。複合インデックスの列の順番はパフォーマンスに直結するため、必ず確認が必要です。
| 列名 | データ型 | 説明 |
|---|---|---|
| INDEX_NAME | VARCHAR2(128) | インデックス名 |
| TABLE_NAME | VARCHAR2(128) | 所属するテーブル名 |
| COLUMN_NAME | VARCHAR2(4000) | 構成する列名(FBI の場合は SYS_NC00xxx$ 形式の内部名) |
| COLUMN_POSITION | NUMBER | 複合インデックス内での列の順序(1 が先頭) |
| COLUMN_LENGTH | NUMBER | 列のバイト長 |
| CHAR_LENGTH | NUMBER | 文字長 |
| DESCEND | VARCHAR2(4) | 降順ソートかどうか(DESC / ASC) |
-- テーブルのインデックスと構成列を COLUMN_POSITION 順に表示
SELECT i.index_name,
i.uniqueness,
i.index_type,
c.column_position,
c.column_name,
c.descend
FROM user_indexes i
JOIN user_ind_columns c ON i.index_name = c.index_name
WHERE i.table_name = 'EMPLOYEES'
ORDER BY i.index_name, c.column_position;
-- 複合インデックスの列を文字列で結合して一覧化(確認しやすい形式)
SELECT index_name,
table_name,
LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_position) AS columns,
COUNT(*) AS col_count
FROM user_ind_columns
GROUP BY index_name, table_name
ORDER BY table_name, index_name;
USER_IND_EXPRESSIONS:ファンクションベースインデックスの式を確認
ファンクションベースインデックス(FBI)では、USER_IND_COLUMNS のCOLUMN_NAME に SYS_NC00xxx$ のような内部名が表示されます。実際の式(関数)は USER_IND_EXPRESSIONS で確認します。
-- FBI の式を確認(USER_IND_COLUMNS と結合)
SELECT c.index_name,
c.column_position,
CASE
WHEN e.column_expression IS NOT NULL THEN TO_CHAR(e.column_expression)
ELSE c.column_name
END AS actual_column
FROM user_ind_columns c
LEFT JOIN user_ind_expressions e
ON c.index_name = e.index_name
AND c.column_position = e.column_position
WHERE c.table_name = 'EMPLOYEES'
ORDER BY c.index_name, c.column_position;
FBI が有効化されるには
QUERY_REWRITE_ENABLED = TRUE(デフォルト有効)かつクエリの WHERE 句にインデックス作成時と完全に同一の式が使われている必要があります。UPPER(email) で作成した FBI は LOWER(email) では使われません。インデックスの作成・設計方法の詳細は【Oracle】インデックスの作成・再構築・削除完全ガイドを参照してください。V$OBJECT_USAGE:インデックスの使用状況を監視する
V$OBJECT_USAGE は ALTER INDEX … MONITORING USAGE で有効にした監視のレポートを保持するビューです。インデックスが実際にクエリで使われているかを確認し、不要なインデックスを特定するのに使います。
-- Step 1: 監視を開始する
ALTER INDEX idx_emp_email MONITORING USAGE;
-- Step 2: 一定期間(例:1 週間以上)通常の負荷をかける
-- Step 3: 使用状況を確認する
SELECT index_name,
table_name,
monitoring, -- YES / NO(現在監視中かどうか)
used, -- YES / NO(監視期間中に使われたかどうか)
start_monitoring,
end_monitoring
FROM v$object_usage
ORDER BY table_name, index_name;
-- Step 4: 監視を停止する
ALTER INDEX idx_emp_email NOMONITORING USAGE;
| 列名 | 値 | 意味 |
|---|---|---|
| MONITORING | YES | 現在監視が有効な状態 |
| MONITORING | NO | 監視が停止された状態(過去の結果は残る) |
| USED | YES | 監視期間中に少なくとも 1 回クエリで使用された |
| USED | NO | 監視期間中に一度も使用されなかった(削除候補) |
①
USED = NO だからといって即座に削除すると危険です。監視期間が短すぎた・月次バッチなど低頻度処理でのみ使われる・UNIQUE インデックス(制約の担保用)の可能性があります。②
ALTER INDEX … MONITORING USAGE を再実行すると履歴がリセットされます。③ UNIQUE 制約・PRIMARY KEY 制約のインデックスは
USED = NO でも削除してはいけません。USER_INDEXES.UNIQUENESS = 'UNIQUE' のものは制約の確認が必要です。INDEX_STATS:ブロック使用率と断片化を確認する
ANALYZE INDEX … VALIDATE STRUCTURE を実行すると、INDEX_STATS ビューに詳細なブロック統計が書き込まれます。ただし、このコマンドは対象インデックス全体をスキャンするため本番では実行コストに注意してください。
-- インデックスの詳細ブロック統計を取得(実行後 INDEX_STATS に結果が入る)
ANALYZE INDEX idx_emp_dept VALIDATE STRUCTURE;
-- INDEX_STATS から結果を確認
SELECT name,
height, -- B-tree の深さ
blocks, -- 総ブロック数(アロケート済み)
lf_rows, -- リーフブロックのレコード数
lf_blks, -- リーフブロック数
del_lf_rows, -- 削除済みレコード数(これが多いと断片化)
ROUND(del_lf_rows / NULLIF(lf_rows + del_lf_rows, 0) * 100, 2) AS del_ratio_pct,
br_rows, -- ブランチブロックのレコード数
br_blks, -- ブランチブロック数
rows_per_key -- キー値あたりの平均行数(低いほど選択性が高い)
FROM index_stats;
削除済みレコードの割合(
del_lf_rows / (lf_rows + del_lf_rows) × 100)が20% を超える場合は REBUILD の検討対象になります。インデックスの再構築(REBUILD / COALESCE)の詳細は【Oracle】インデックスの作成・再構築・削除完全ガイドを参照してください。重複インデックスを SQL で検出する
同じ列構成(または先頭列が重複する)インデックスが複数存在すると、INSERT/UPDATE/DELETE のたびに無駄なメンテナンスコストが発生します。以下の SQL で重複・冗長インデックスを発見できます。
-- 同じテーブルの同じ列構成のインデックスを検出
SELECT a.table_name,
a.index_name AS idx1,
b.index_name AS idx2,
a.columns AS col_list
FROM (
SELECT index_name,
table_name,
LISTAGG(column_name, ',') WITHIN GROUP (ORDER BY column_position) AS columns
FROM user_ind_columns
GROUP BY index_name, table_name
) a
JOIN (
SELECT index_name,
table_name,
LISTAGG(column_name, ',') WITHIN GROUP (ORDER BY column_position) AS columns
FROM user_ind_columns
GROUP BY index_name, table_name
) b
ON a.table_name = b.table_name
AND a.columns = b.columns
AND a.index_name < b.index_name
ORDER BY a.table_name;
-- インデックス A の先頭 N 列がインデックス B と一致する場合を検出
-- 例: IDX_1(A, B, C) と IDX_2(A, B) は IDX_2 が IDX_1 に包含されている
SELECT a.table_name,
a.index_name AS redundant_idx, -- 削除候補(包含される方)
b.index_name AS covering_idx, -- 包含する方(残す側)
a.col_count AS redundant_cols,
b.col_count AS covering_cols
FROM (
SELECT index_name, table_name,
LISTAGG(column_name, ',') WITHIN GROUP (ORDER BY column_position) AS cols,
COUNT(*) AS col_count
FROM user_ind_columns GROUP BY index_name, table_name
) a
JOIN (
SELECT index_name, table_name,
LISTAGG(column_name, ',') WITHIN GROUP (ORDER BY column_position) AS cols,
COUNT(*) AS col_count
FROM user_ind_columns GROUP BY index_name, table_name
) b
ON a.table_name = b.table_name
AND a.index_name != b.index_name
AND a.col_count < b.col_count
AND b.cols LIKE a.cols || ',%'
ORDER BY a.table_name, a.index_name;
「冗長」に見えても、短い方のインデックスが大量の INSERT/UPDATE でよく使われる場合は削除するとかえって遅くなることがあります。
V$OBJECT_USAGE で十分な期間監視して USED = NO を確認してから削除してください。不要インデックスの特定・削除手順の詳細は【Oracle】不要なインデックスを特定・削除する方法を参照してください。INVISIBLE インデックスの確認(Oracle 11g 以降)
INVISIBLE インデックスはオプティマイザから見えない状態のインデックスです。削除前の影響確認や、段階的な有効化のテストに使われます。
-- INVISIBLE インデックスを一覧表示 SELECT index_name, table_name, status, visibility FROM user_indexes WHERE visibility = 'INVISIBLE' ORDER BY table_name; -- INVISIBLE → VISIBLE に変更(オプティマイザが再び使えるようになる) ALTER INDEX idx_emp_name VISIBLE; -- VISIBLE → INVISIBLE に変更(削除前の影響確認に使う) ALTER INDEX idx_emp_name INVISIBLE; -- セッションレベルで INVISIBLE インデックスを有効にしてテスト ALTER SESSION SET optimizer_use_invisible_indexes = TRUE;
DBA_IND_PARTITIONS:パーティションインデックスを確認する
パーティションテーブルのインデックスは、DBA_IND_PARTITIONS でパーティションごとの状態・統計を確認できます。パーティション操作(TRUNCATE / EXCHANGE / DROP)後にUNUSABLE になったパーティションインデックスの特定に使います。
-- UNUSABLE なインデックスパーティションを検出(再構築が必要)
SELECT index_owner,
index_name,
partition_name,
status,
num_rows,
last_analyzed
FROM dba_ind_partitions
WHERE status = 'UNUSABLE'
ORDER BY index_owner, index_name, partition_name;
-- UNUSABLE インデックスパーティションを一括再構築
-- ALTER INDEX idx_name REBUILD PARTITION part_name;
DBMS_METADATA.GET_DDL:インデックスの CREATE 文を取得する
DBMS_METADATA.GET_DDL を使うと、既存インデックスの CREATE 文を取得できます。環境移行・ドキュメント化・インデックスの再作成スクリプト生成に役立ちます。
-- 特定インデックスの DDL を取得
SELECT DBMS_METADATA.GET_DDL('INDEX', 'IDX_EMP_DEPT') AS ddl
FROM dual;
-- スキーマ内の全インデックスの DDL を一括取得
BEGIN
FOR idx IN (
SELECT index_name FROM user_indexes ORDER BY index_name
) LOOP
DBMS_OUTPUT.PUT_LINE('-- ' || idx.index_name);
DBMS_OUTPUT.PUT_LINE(
DBMS_METADATA.GET_DDL('INDEX', idx.index_name)
);
DBMS_OUTPUT.PUT_LINE('/');
END LOOP;
END;
/
-- 別スキーマのインデックスの DDL を取得(DBA 権限必要)
SELECT DBMS_METADATA.GET_DDL('INDEX', 'IDX_EMP_DEPT', 'HR') AS ddl FROM dual;
実践的な確認パターン集
-- 30日以上統計情報が更新されていないインデックス
SELECT index_name, table_name, last_analyzed,
TRUNC(SYSDATE - last_analyzed) AS days_since_analyzed
FROM user_indexes
WHERE last_analyzed < SYSDATE - 30
OR last_analyzed IS NULL -- 一度も統計収集されていない
ORDER BY last_analyzed NULLS FIRST;
-- STATUS = UNUSABLE のインデックスを検出(再構築が必要) SELECT owner, index_name, table_name, status, last_analyzed FROM dba_indexes WHERE status = 'UNUSABLE' ORDER BY owner, table_name;
-- テーブルごとのインデックス数とリーフブロックの合計(大きい順)
SELECT table_name,
COUNT(*) AS index_count,
SUM(leaf_blocks) AS total_leaf_blocks
FROM user_indexes
GROUP BY table_name
ORDER BY index_count DESC;
よくある質問
ALTER INDEX インデックス名 REBUILD; で再構築すると VALID に戻ります。UNUSABLE になる主な原因は、パーティション操作(TRUNCATE PARTITION / EXCHANGE PARTITION)後や、SKIP_UNUSABLE_INDEXES = TRUE の環境で大量 INSERT が行われた場合です。USER_IND_COLUMNS.COLUMN_NAME には内部的に生成された仮想列名(SYS_NC00xxx$)が表示されます。実際の関数式を確認するには USER_IND_EXPRESSIONS を使い、COLUMN_POSITION でジョインしてください。USER_INDEXES.UNIQUENESS = 'UNIQUE' のインデックスは制約と連動していることが多いため、削除前に USER_CONSTRAINTS で確認してください。ORA-31603 が出る場合はオブジェクト名が見つからない(大文字小文字・スキーマ名の誤り)か、アクセス権限がないケースが多いです。インデックス名は Oracle 内部では大文字で格納されているため、必ず大文字で指定してください。別スキーマのインデックスを取得する場合は SELECT ANY DICTIONARY 権限が必要です。まとめ
Oracle インデックス情報の取得方法をまとめます。
| 確認したいこと | 使うビュー・方法 |
|---|---|
| インデックスの種類・状態・統計情報 | USER_INDEXES / DBA_INDEXES(STATUS・VISIBILITY・CLUSTERING_FACTOR等) |
| インデックスの構成列・順序・降順 | USER_IND_COLUMNS(COLUMN_POSITION・DESCEND) |
| ファンクションベースインデックスの式 | USER_IND_EXPRESSIONS(COLUMN_EXPRESSION) |
| インデックスが実際に使われているか | V$OBJECT_USAGE(MONITORING USAGE 有効後に USED を確認) |
| インデックスのブロック使用率・断片化率 | INDEX_STATS(ANALYZE INDEX VALIDATE STRUCTURE を実行後) |
| パーティションインデックスの UNUSABLE 確認 | DBA_IND_PARTITIONS(STATUS = UNUSABLE を検索) |
| 重複・冗長なインデックスを検出する | USER_IND_COLUMNS を自己結合して構成列を比較 |
| 統計情報が古いインデックスを検出する | USER_INDEXES の LAST_ANALYZED で日数フィルタ |
| インデックスの CREATE 文を取得する | DBMS_METADATA.GET_DDL(‘INDEX’, インデックス名) |
| INVISIBLE インデックスを確認・切り替える | USER_INDEXES.VISIBILITY + ALTER INDEX … VISIBLE/INVISIBLE |
インデックスの作成・再構築・削除については【Oracle】インデックスの作成・再構築・削除完全ガイド、使用頻度の低い不要なインデックスの特定・削除については【Oracle】不要なインデックスを特定・削除する方法を参照してください。

