【Oracle】インデックス情報を取得する方法完全ガイド|USER_INDEXES・統計情報・重複検出・DDL取得まで解説

【Oracle】インデックス情報を取得する方法完全ガイド|USER_INDEXES・統計情報・重複検出・DDL取得まで解説 Oracle

「このテーブルにインデックスが張られているか?」「複合インデックスの列の順番は正しいか?」「インデックスは本当に使われているのか?」――チューニング作業では、まずインデックスの現状を正確に把握することが出発点です。

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 自分のスキーマのインデックスのみ インデックスの状態・統計を確認(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)
SQL(USER_INDEXES:テーブルのインデックス一覧を確認)
-- 特定テーブルのインデックス一覧(状態・統計含む)
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;
SQL(DBA_INDEXES:スキーマを指定してインデックスを確認)
-- 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 より大きい かなりランダムで断片化している可能性 ✗ フルテーブルスキャンの方が速い場合がある
CLUSTERING_FACTOR を確認する実践的な SQL
選択性(SELECT 件数 / 全件数)と CLUSTERING_FACTOR を組み合わせてインデックス効率を評価できます。
SQL(インデックス効率の評価: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)
SQL(テーブルのインデックスと構成列を一覧表示)
-- テーブルのインデックスと構成列を 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;
SQL(複合インデックスの列順を確認:インデックス名でグルーピング)
-- 複合インデックスの列を文字列で結合して一覧化(確認しやすい形式)
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_COLUMNSCOLUMN_NAMESYS_NC00xxx$ のような内部名が表示されます。実際の式(関数)は USER_IND_EXPRESSIONS で確認します。

SQL(ファンクションベースインデックスの式を確認)
-- 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(ファンクションベースインデックス)が使われる条件
FBI が有効化されるには QUERY_REWRITE_ENABLED = TRUE(デフォルト有効)かつクエリの WHERE 句にインデックス作成時と完全に同一の式が使われている必要があります。UPPER(email) で作成した FBI は LOWER(email) では使われません。インデックスの作成・設計方法の詳細は【Oracle】インデックスの作成・再構築・削除完全ガイドを参照してください。

V$OBJECT_USAGE:インデックスの使用状況を監視する

V$OBJECT_USAGEALTER INDEX … MONITORING USAGE で有効にした監視のレポートを保持するビューです。インデックスが実際にクエリで使われているかを確認し、不要なインデックスを特定するのに使います。

SQL(インデックスの使用状況の監視を開始・確認・停止)
-- 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 監視期間中に一度も使用されなかった(削除候補)
V$OBJECT_USAGE の注意点
USED = NO だからといって即座に削除すると危険です。監視期間が短すぎた・月次バッチなど低頻度処理でのみ使われる・UNIQUE インデックス(制約の担保用)の可能性があります。
ALTER INDEX … MONITORING USAGE を再実行すると履歴がリセットされます。
③ UNIQUE 制約・PRIMARY KEY 制約のインデックスは USED = NO でも削除してはいけません。USER_INDEXES.UNIQUENESS = 'UNIQUE' のものは制約の確認が必要です。

INDEX_STATS:ブロック使用率と断片化を確認する

ANALYZE INDEX … VALIDATE STRUCTURE を実行すると、INDEX_STATS ビューに詳細なブロック統計が書き込まれます。ただし、このコマンドは対象インデックス全体をスキャンするため本番では実行コストに注意してください。

SQL(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 で重複・冗長インデックスを発見できます。

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;
SQL(先頭列が重複する冗長インデックスの検出)
-- インデックス 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 インデックスはオプティマイザから見えない状態のインデックスです。削除前の影響確認や、段階的な有効化のテストに使われます。

SQL(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 になったパーティションインデックスの特定に使います。

SQL(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 文を取得できます。環境移行・ドキュメント化・インデックスの再作成スクリプト生成に役立ちます。

SQL(特定インデックスの CREATE 文を取得)
-- 特定インデックスの DDL を取得
SELECT DBMS_METADATA.GET_DDL('INDEX', 'IDX_EMP_DEPT') AS ddl
FROM   dual;
SQL(スキーマ内のすべてのインデックスの DDL を取得)
-- スキーマ内の全インデックスの 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;

実践的な確認パターン集

SQL(統計情報が古いインデックスを検出)
-- 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;
SQL(UNUSABLE なインデックスを検出)
-- STATUS = UNUSABLE のインデックスを検出(再構築が必要)
SELECT owner, index_name, table_name, status, last_analyzed
FROM   dba_indexes
WHERE  status = 'UNUSABLE'
ORDER  BY owner, table_name;
SQL(テーブルごとのインデックス数と合計サイズ)
-- テーブルごとのインデックス数とリーフブロックの合計(大きい順)
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;

よくある質問

USER_INDEXES と DBA_INDEXES の違いは何ですか?
USER_INDEXES は自分のスキーマのインデックスのみ参照でき、DBA 権限は不要です。DBA_INDEXES はデータベース内のすべてのインデックスを参照でき、SELECT ANY DICTIONARY 権限が必要です。他スキーマのインデックスを参照できる権限はあるが DBA 権限はない場合は ALL_INDEXES を使います。USER_ / ALL_ / DBA_ の三層構造はすべてのデータディクショナリビューで共通のパターンです。
インデックスの STATUS が UNUSABLE になっています。どうすればよいですか?
UNUSABLE になったインデックスはオプティマイザから使われず、DML 操作もエラーになる場合があります。ALTER INDEX インデックス名 REBUILD; で再構築すると VALID に戻ります。UNUSABLE になる主な原因は、パーティション操作(TRUNCATE PARTITION / EXCHANGE PARTITION)後や、SKIP_UNUSABLE_INDEXES = TRUE の環境で大量 INSERT が行われた場合です。
USER_IND_COLUMNS で列名が SYS_NC00xxx$ と表示されます
ファンクションベースインデックス(FBI)の場合、USER_IND_COLUMNS.COLUMN_NAME には内部的に生成された仮想列名(SYS_NC00xxx$)が表示されます。実際の関数式を確認するには USER_IND_EXPRESSIONS を使い、COLUMN_POSITION でジョインしてください。
V$OBJECT_USAGE で USED = NO のインデックスはすぐに削除してよいですか?
慎重に判断してください。監視期間が短い・月次/年次バッチでのみ使用・UNIQUE 制約の担保用(USED = NO でも制約として必要)などのケースがあります。最低でも本番の 1 業務サイクル(月次・年次含む)を監視してから判断することを推奨します。また USER_INDEXES.UNIQUENESS = 'UNIQUE' のインデックスは制約と連動していることが多いため、削除前に USER_CONSTRAINTS で確認してください。
CLUSTERING_FACTOR が大きいとどうすればよいですか?
CLUSTERING_FACTOR が BLOCKS に近い場合、そのインデックスによるレンジスキャンが非効率です。①テーブルの物理的な行順序を変更する(MOVE + インデックス再構築、または DBMS_REDEFINITION)、②インデックスクラスタ化テーブル(IOT: Index Organized Table)に変更する、③そのクエリパターンでフルテーブルスキャンが適切かどうかをヒントで検証する、のいずれかの対処を検討してください。
DBMS_METADATA.GET_DDL でエラーになります
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】不要なインデックスを特定・削除する方法を参照してください。