【完全ガイド】ORA-01502: index or partition of such index is in unusable state の原因と解決方法|Direct Path Insert・パーティション・REBUILD 徹底解説

【完全ガイド】ORA-01502: index or partition of such index is in unusable state の原因と解決方法|Direct Path Insert・パーティション・REBUILD 徹底解説

Oracle DBA が本番運用中に遭遇するインデックス系の重要エラー:

SELECT * FROM orders WHERE order_id = 12345;
*
ERROR at line 1:
ORA-01502: index 'APP.ORDERS_PK' or partition of such index 
           is in unusable state

**「インデックスまたはパーティションが使用不可状態」**というシンプルなメッセージ。インデックスに依存する SQL 全停止の重大な問題です:

  • PRIMARY KEY / UNIQUE 制約のインデックスが UNUSABLE → DML 全停止
  • 通常のインデックスが UNUSABLE → SELECT パフォーマンス激低下
  • パーティションインデックスの一部 UNUSABLE → 該当パーティションのみ問題
  • DBA 緊急対応が必要
  • アプリ全体への影響

このエラーの本質は、Oracle がインデックスを「マーク UNUSABLE」状態と判定して使用拒否:

インデックスの STATUS:
  VALID:    通常
  UNUSABLE: 使用不可(本記事)
  N/A:      パーティション・インデックス本体

UNUSABLE になる契機:
  - Direct Path Insert (SQL*Loader)
  - ALTER TABLE MOVE
  - パーティション DDL
  - ALTER INDEX ... UNUSABLE 明示
  - REBUILD 失敗・中断
  - 領域不足

目次

インデックスの STATUS 階層

Oracle パーティションインデックスの STATUS:

【通常インデックス】
DBA_INDEXES.STATUS:
  VALID / UNUSABLE

【パーティションインデックス】
DBA_INDEXES.STATUS:      N/A (常に)
DBA_IND_PARTITIONS.STATUS: USABLE / UNUSABLE / N/A

【サブパーティションインデックス】
DBA_IND_SUBPARTITIONS.STATUS: USABLE / UNUSABLE

PRIMARY KEY 制約のインデックス UNUSABLE の影響

重大な影響:

PRIMARY KEY 制約 → 内部で UNIQUE INDEX 使用
   ↓
UNIQUE INDEX が UNUSABLE
   ↓
INSERT / UPDATE / DELETE 不可
   ↓
アプリ完全停止

CA Clarity(PPM ソフトウェア)等の企業アプリでは深刻な障害として頻繁に報告されています:

[CA Clarity][Oracle JDBC Driver]
ORA-01502: index 'NIKU.CMN_SEC_ASSGND_OBJ_PERM_PK' 
           or partition of such index is in unusable state

現場で最も典型的なパターン:

  • Direct Path Insert(SQL*Loader DIRECT=Y(最頻出)
  • ALTER TABLE MOVE 後の INDEX 再構築忘れ
  • パーティション DDL(SPLIT/MERGE/TRUNCATE/MOVE/EXCHANGE)
  • ALTER INDEX ... UNUSABLE 明示(メンテナンス用途)
  • Data Pump インポートimpdp
  • REBUILD 中断(領域不足・接続断)
  • 一意制約違反CREATE UNIQUE INDEX 失敗)
  • 一時領域不足REBUILD 中)
  • IOT 再編成
  • System crash 中の DDL

多くの日本語記事が「REBUILD せよ」で終わりますが、実務では:

  • REBUILD vs REBUILD ONLINE選択基準
  • PARTITION 単位の REBUILDREBUILD PARTITION
  • SKIP_UNUSABLE_INDEXES セッション設定の使い所
  • UPDATE INDEXES clause(DDL 時の自動再構築)
  • Global vs Local パーティションインデックスの違い
  • ONLINE REBUILD業務停止最小化
  • PARALLEL オプションによる並列化
  • NOLOGGING の使い所(バックアップ必須)
  • DBA_IND_PARTITIONS vs DBA_IND_SUBPARTITIONS
  • Rails マイグレーションでの注意点

さらに、Oracle 公式が明示する重要な仕様:

オプティマイザが UNUSABLE インデックスを選択:
  SKIP_UNUSABLE_INDEXES=TRUE (デフォルト): 
    → 使わない、フルスキャン
  
  ヒントで強制:
    → ORA-01502 発生

本記事では、ORA-01502: index or partition of such index is in unusable state完全な原因と解決方法を、リファレンスとして実用的に整理します。10大発生パターン、REBUILD 戦略、6つの解決策、パーティション対応、Rails/Java/Python 対応、実践シナリオ、FAQまで完全網羅。この1本で ORA-01502 に冷静に的確に対処できるようになります。


結論:ALTER INDEX … REBUILD で復旧

時間がない方向けに、最速の対処を先に示します。

エラーメッセージの読み方

ORA-01502: index 'SCHEMA.INDEX_NAME' or partition of such index 
           is in unusable state
                    ↑
             使用不可のインデックス

= インデックスが UNUSABLE マーク済み
  DBA_INDEXES.STATUS または DBA_IND_PARTITIONS.STATUS で確認

最速の診断と対処

-- STEP 1: UNUSABLE インデックス一覧
SELECT owner, index_name, status
FROM dba_indexes
WHERE status = 'UNUSABLE';

-- STEP 2: UNUSABLE パーティション
SELECT index_owner, index_name, partition_name, status
FROM dba_ind_partitions
WHERE status = 'UNUSABLE';

-- STEP 3: REBUILD 実行
ALTER INDEX schema.index_name REBUILD;

-- STEP 4: PARTITION 単位
ALTER INDEX schema.index_name REBUILD PARTITION partition_name;

-- STEP 5: ONLINE で業務中対応
ALTER INDEX schema.index_name REBUILD ONLINE;

6つの解決策

#手法使う場面
REBUILD基本
REBUILD ONLINE業務中
REBUILD PARTITIONパーティション
一括スクリプト大量 UNUSABLE
SKIP_UNUSABLE_INDEXES一時回避
UPDATE INDEXESDDL 時予防

パーティションインデックス STATUS

ビューSTATUS 意味
DBA_INDEXESVALID / UNUSABLE / N/A
DBA_IND_PARTITIONSUSABLE / UNUSABLE / N/A
DBA_IND_SUBPARTITIONSUSABLE / UNUSABLE

詳細は以下で解説します。


Oracle インデックスの仕組み

インデックスの種類

通常インデックス(B-Tree):

CREATE INDEX idx_emp_name ON emp(name);

一意インデックス:

CREATE UNIQUE INDEX idx_emp_id ON emp(id);

パーティションインデックス:

-- Local(テーブルパーティションに追従)
CREATE INDEX idx_orders_date ON orders(order_date) LOCAL;

-- Global(独立)
CREATE INDEX idx_orders_id ON orders(id) GLOBAL
  PARTITION BY RANGE(id) (
    PARTITION p1 VALUES LESS THAN (10000),
    PARTITION p2 VALUES LESS THAN (MAXVALUE)
  );

Bitmap インデックス:

CREATE BITMAP INDEX idx_status ON emp(status);

Function-Based インデックス:

CREATE INDEX idx_upper_name ON emp(UPPER(name));

インデックスの STATUS

-- 通常
SELECT index_name, status FROM user_indexes;
-- VALID / UNUSABLE

-- パーティション
SELECT index_name, partition_name, status FROM user_ind_partitions;
-- USABLE / UNUSABLE / N/A

-- サブパーティション
SELECT index_name, subpartition_name, status FROM user_ind_subpartitions;

Global vs Local

Global(テーブルパーティションと独立):

  • 1 index = 全パーティション対応
  • パーティション操作で全体 UNUSABLE
  • 更新頻度低いテーブル向け

Local(テーブルパーティションと 1:1):

  • 各テーブルパーティションに対応 index パーティション
  • パーティション操作で該当のみ影響
  • 大規模テーブル向け(推奨)

【原因①】Direct Path Insert(最頻出)

シナリオ

# SQL*Loader Direct Path モード
$ sqlldr scott/tiger control=data.ctl direct=y

または SQL:

INSERT /*+ APPEND */ INTO orders SELECT * FROM staging;

結果:

Direct Path Load 完了
Records loaded: 1,000,000
Indexes in use: 0
Index maintained: INDEX X marked UNUSABLE

Direct Path は インデックスをスキップ、後で REBUILD 必要。

診断

SELECT index_name, status 
FROM user_indexes 
WHERE table_name = 'ORDERS' AND status = 'UNUSABLE';

解決

ALTER INDEX orders_pk REBUILD;
ALTER INDEX orders_idx_date REBUILD ONLINE;

【原因②】ALTER TABLE MOVE

シナリオ

-- テーブル移動
ALTER TABLE orders MOVE TABLESPACE users;
-- インデックス UNUSABLE に!

診断

SELECT index_name, status FROM user_indexes 
WHERE table_name = 'ORDERS';
-- 複数 UNUSABLE

解決

A. 個別 REBUILD:

ALTER INDEX orders_pk REBUILD;
ALTER INDEX orders_idx1 REBUILD;

B. 一括スクリプト:

BEGIN
  FOR r IN (SELECT index_name FROM user_indexes 
            WHERE table_name = 'ORDERS' AND status = 'UNUSABLE') LOOP
    EXECUTE IMMEDIATE 'ALTER INDEX ' || r.index_name || ' REBUILD';
  END LOOP;
END;
/

C. 予防:MOVE 時に自動再構築(12.2+):

ALTER TABLE orders MOVE TABLESPACE users UPDATE INDEXES;
-- インデックス自動再構築

【原因③】パーティション DDL

シナリオ

-- SPLIT PARTITION
ALTER TABLE orders SPLIT PARTITION p_2025 AT (TO_DATE('2025-07-01', 'YYYY-MM-DD'))
  INTO (PARTITION p_2025_h1, PARTITION p_2025_h2);
-- Global index UNUSABLE!

-- MOVE PARTITION
ALTER TABLE orders MOVE PARTITION p_2024 TABLESPACE ts_2024;

-- TRUNCATE PARTITION
ALTER TABLE orders TRUNCATE PARTITION p_2023;

-- EXCHANGE PARTITION
ALTER TABLE orders EXCHANGE PARTITION p_2024 WITH TABLE archive_2024;

診断

-- パーティション単位
SELECT index_owner, index_name, partition_name, status
FROM dba_ind_partitions
WHERE status = 'UNUSABLE';

解決

A. パーティション単位 REBUILD:

ALTER INDEX orders_idx_date REBUILD PARTITION p_2025_h1;

B. 一括:

BEGIN
  FOR r IN (SELECT index_owner, index_name, partition_name 
            FROM dba_ind_partitions WHERE status = 'UNUSABLE') LOOP
    EXECUTE IMMEDIATE 
      'ALTER INDEX ' || r.index_owner || '.' || r.index_name || 
      ' REBUILD PARTITION ' || r.partition_name;
  END LOOP;
END;
/

C. 予防:DDL 時に UPDATE INDEXES / UPDATE GLOBAL INDEXES:

ALTER TABLE orders SPLIT PARTITION p_2025 AT (TO_DATE('2025-07-01', 'YYYY-MM-DD'))
  INTO (PARTITION p_2025_h1, PARTITION p_2025_h2)
  UPDATE INDEXES;
-- または
ALTER TABLE orders SPLIT PARTITION ...
  UPDATE GLOBAL INDEXES;

【原因④】ALTER INDEX … UNUSABLE 明示

シナリオ(意図的、大量ロード前)

-- ロード前にインデックス無効化(高速化)
ALTER INDEX orders_idx1 UNUSABLE;

-- 大量 INSERT
INSERT INTO orders SELECT * FROM staging;

-- ロード後 REBUILD
ALTER INDEX orders_idx1 REBUILD;

メリット: INSERT パフォーマンス大幅向上。 注意: REBUILD 忘れると ORA-01502。

解決

必ず REBUILD:

ALTER INDEX orders_idx1 REBUILD;

【原因⑤】Data Pump インポート

シナリオ

$ impdp scott/tiger dumpfile=data.dmp
-- インポート中にエラー発生
-- 一部インデックス UNUSABLE

解決

-- UNUSABLE 特定
SELECT owner, index_name FROM dba_indexes WHERE status = 'UNUSABLE';

-- REBUILD
BEGIN
  FOR r IN (SELECT owner, index_name FROM dba_indexes 
            WHERE status = 'UNUSABLE') LOOP
    EXECUTE IMMEDIATE 
      'ALTER INDEX ' || r.owner || '.' || r.index_name || ' REBUILD';
  END LOOP;
END;
/

Data Pump 関連は Oracle Data Pump 使い方の記事も参照してください。


【原因⑥】REBUILD 中断

シナリオ

1. ALTER INDEX big_idx REBUILD;
2. 実行中に接続断 or セッションキル
3. インデックスが UNUSABLE のまま残る

診断

SELECT index_name, status FROM user_indexes 
WHERE status = 'UNUSABLE' AND index_name = 'BIG_IDX';

解決

-- 再度 REBUILD
ALTER INDEX big_idx REBUILD;

-- 領域不足なら別 TS
ALTER INDEX big_idx REBUILD TABLESPACE new_ts;

領域関連は ORA-01654: unable to extend index の記事も参照してください。


【原因⑦】一意制約違反(CREATE UNIQUE INDEX 失敗)

シナリオ

-- 重複データがあるカラムに UNIQUE INDEX
CREATE UNIQUE INDEX idx_unique ON emp(email);
-- ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found

-- インデックスが UNUSABLE で残る場合あり

診断

-- 重複データ確認
SELECT email, COUNT(*) FROM emp GROUP BY email HAVING COUNT(*) > 1;

解決

-- 重複解消後 REBUILD
UPDATE emp SET email = email || ROWNUM WHERE ROWNUM = 1 AND email = 'dup@example.com';

-- REBUILD
ALTER INDEX idx_unique REBUILD;

【原因⑧】領域不足

シナリオ

ALTER INDEX big_idx REBUILD;
-- ORA-01654: unable to extend index by 128 in tablespace INDEXES

-- インデックスが UNUSABLE で残る

解決

A. 別 TS で REBUILD:

ALTER INDEX big_idx REBUILD TABLESPACE new_index_ts;

B. TS 拡張:

ALTER TABLESPACE indexes ADD DATAFILE '/u01/oradata/indexes_02.dbf' SIZE 5G;
ALTER INDEX big_idx REBUILD;

【原因⑨】IOT (Index-Organized Table) 再編成

シナリオ

-- IOT MOVE
ALTER TABLE iot_table MOVE;
-- 関連 index UNUSABLE

解決

ALTER TABLE iot_table MOVE UPDATE INDEXES;

【原因⑩】システムクラッシュ中の DDL

シナリオ

1. ALTER INDEX ... REBUILD 実行中
2. サーバー電源断
3. インデックス UNUSABLE のまま

解決

再度 REBUILD:

ALTER INDEX big_idx REBUILD;

Instance Recovery 後の状態確認:

SELECT index_name, status FROM user_indexes WHERE status = 'UNUSABLE';

DB 起動関連は ORA-01102: cannot mount database の記事も参照してください。


診断ツール完全リファレンス

DBA_INDEXES

-- 全 UNUSABLE インデックス
SELECT owner, index_name, table_name, status, tablespace_name
FROM dba_indexes
WHERE status = 'UNUSABLE'
ORDER BY owner, table_name, index_name;

DBA_IND_PARTITIONS

-- UNUSABLE パーティション
SELECT index_owner, index_name, partition_name, status, tablespace_name
FROM dba_ind_partitions
WHERE status = 'UNUSABLE'
ORDER BY index_owner, index_name, partition_name;

DBA_IND_SUBPARTITIONS

SELECT index_owner, index_name, partition_name, subpartition_name, status
FROM dba_ind_subpartitions
WHERE status = 'UNUSABLE';

USER_INDEXES(自スキーマ)

SELECT index_name, table_name, status
FROM user_indexes
WHERE status = 'UNUSABLE';

REBUILD 用スクリプト生成

-- 通常インデックス
SELECT 'ALTER INDEX ' || owner || '.' || index_name || ' REBUILD ONLINE;' AS cmd
FROM dba_indexes
WHERE status = 'UNUSABLE';

-- パーティション
SELECT 'ALTER INDEX ' || index_owner || '.' || index_name || 
       ' REBUILD PARTITION ' || partition_name || ' ONLINE;' AS cmd
FROM dba_ind_partitions
WHERE status = 'UNUSABLE';

6つの解決策 完全リファレンス

解決策① ALTER INDEX … REBUILD

-- 基本
ALTER INDEX schema.index_name REBUILD;

-- 別テーブルスペースへ
ALTER INDEX schema.index_name REBUILD TABLESPACE new_ts;

-- 並列(大規模テーブル)
ALTER INDEX schema.index_name REBUILD PARALLEL 4;

-- NOLOGGING(バックアップ必須)
ALTER INDEX schema.index_name REBUILD NOLOGGING;

解決策② ALTER INDEX … REBUILD ONLINE

-- ONLINE = 業務中に実行可
ALTER INDEX schema.index_name REBUILD ONLINE;

-- 並列 + ONLINE
ALTER INDEX schema.index_name REBUILD ONLINE PARALLEL 4;

メリット: 業務停止不要。 注意: 若干の負荷 + 一時領域増加。

解決策③ REBUILD PARTITION

-- パーティション単位
ALTER INDEX orders_idx REBUILD PARTITION p_2025;

-- ONLINE
ALTER INDEX orders_idx REBUILD PARTITION p_2025 ONLINE;

-- サブパーティション
ALTER INDEX orders_idx REBUILD SUBPARTITION sp_2025_q1;

解決策④ 一括スクリプト

-- 全 UNUSABLE インデックス REBUILD
BEGIN
  FOR r IN (SELECT owner, index_name FROM dba_indexes 
            WHERE status = 'UNUSABLE'
              AND owner NOT IN ('SYS', 'SYSTEM')) LOOP
    BEGIN
      EXECUTE IMMEDIATE 
        'ALTER INDEX ' || r.owner || '.' || r.index_name || ' REBUILD ONLINE';
      DBMS_OUTPUT.PUT_LINE('OK: ' || r.owner || '.' || r.index_name);
    EXCEPTION
      WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('FAIL: ' || r.owner || '.' || r.index_name || 
                              ' - ' || SQLERRM);
    END;
  END LOOP;
END;
/

-- パーティション版
BEGIN
  FOR r IN (SELECT index_owner, index_name, partition_name 
            FROM dba_ind_partitions 
            WHERE status = 'UNUSABLE'
              AND index_owner NOT IN ('SYS', 'SYSTEM')) LOOP
    BEGIN
      EXECUTE IMMEDIATE 
        'ALTER INDEX ' || r.index_owner || '.' || r.index_name || 
        ' REBUILD PARTITION ' || r.partition_name || ' ONLINE';
    EXCEPTION
      WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('FAIL: ' || r.index_name || '.' || r.partition_name);
    END;
  END LOOP;
END;
/

解決策⑤ SKIP_UNUSABLE_INDEXES セッション設定

Oracle 10g+ でデフォルト TRUE、一時回避:

-- セッションレベル
ALTER SESSION SET SKIP_UNUSABLE_INDEXES = TRUE;

-- システムレベル
ALTER SYSTEM SET SKIP_UNUSABLE_INDEXES = TRUE;

-- 効果:
-- UNUSABLE インデックスをオプティマイザが選択しない
-- フルスキャンで代替(低速)
-- PRIMARY KEY/UNIQUE のみは違反時 ORA-01502 発生

⚠️ 根本解決ではない、REBUILD 必須。

解決策⑥ UPDATE INDEXES clause(予防)

DDL 時にインデックス自動再構築:

-- ALTER TABLE MOVE + 自動再構築
ALTER TABLE orders MOVE TABLESPACE new_ts UPDATE INDEXES;

-- パーティション DDL + 自動再構築
ALTER TABLE orders SPLIT PARTITION p_2025 AT (...)
  INTO (PARTITION p1, PARTITION p2)
  UPDATE INDEXES;

-- UPDATE GLOBAL INDEXES(グローバルのみ)
ALTER TABLE orders TRUNCATE PARTITION p_2023 UPDATE GLOBAL INDEXES;

Rails / Java / Python 対応

Rails ActiveRecord

エラーハンドリング:

begin
  Order.find(order_id)
rescue ActiveRecord::StatementInvalid => e
  if e.message.include?("ORA-01502")
    Rails.logger.fatal "Index unusable: #{e.message}"
    NotifyOps.critical("DBA action required: rebuild index")
  end
end

マイグレーション後の検証:

class MoveOrdersTable < ActiveRecord::Migration[8.0]
  def up
    execute "ALTER TABLE orders MOVE TABLESPACE users UPDATE INDEXES"
    
    # インデックス状態確認
    unusable = execute(<<-SQL).to_a
      SELECT index_name FROM user_indexes 
      WHERE table_name = 'ORDERS' AND status = 'UNUSABLE'
    SQL
    
    if unusable.any?
      raise "Unusable indexes: #{unusable.inspect}"
    end
  end
end

Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、rails db:migrate 使い方の記事も参照してください。

Java (JDBC)

try {
    ResultSet rs = ps.executeQuery();
} catch (SQLException e) {
    if (e.getErrorCode() == 1502) {
        logger.fatal("Index unusable: " + e.getMessage());
        // DBA 通知 + フルスキャンにフォールバック検討
    }
}

Python (oracledb)

import oracledb
import logging

try:
    cursor.execute("SELECT * FROM orders WHERE order_id = :1", [12345])
except oracledb.DatabaseError as e:
    error_obj, = e.args
    if error_obj.code == 1502:
        logging.critical(f"Index unusable: {error_obj.message}")
        # 自動 REBUILD スクリプト起動 or 通知

実践シナリオ

シナリオ1:本番緊急対応

-- 1. UNUSABLE 特定
SELECT owner, index_name, table_name 
FROM dba_indexes WHERE status = 'UNUSABLE';

-- 2. パーティション UNUSABLE も
SELECT index_owner, index_name, partition_name 
FROM dba_ind_partitions WHERE status = 'UNUSABLE';

-- 3. 一括 REBUILD ONLINE
BEGIN
  FOR r IN (SELECT owner, index_name FROM dba_indexes 
            WHERE status = 'UNUSABLE') LOOP
    EXECUTE IMMEDIATE 
      'ALTER INDEX ' || r.owner || '.' || r.index_name || ' REBUILD ONLINE';
  END LOOP;
END;
/

-- 4. 検証
SELECT COUNT(*) FROM dba_indexes WHERE status = 'UNUSABLE';
-- 0 なら OK

シナリオ2:Direct Path Insert 後の対応

# 大量ロード
$ sqlldr scott/tiger control=big.ctl direct=y

# 完了後、インデックス確認
sqlplus scott/tiger <<EOF
SELECT index_name, status FROM user_indexes 
WHERE table_name = 'BIG_TABLE' AND status = 'UNUSABLE';
EOF

# REBUILD
sqlplus scott/tiger <<EOF
BEGIN
  FOR r IN (SELECT index_name FROM user_indexes 
            WHERE table_name = 'BIG_TABLE' AND status = 'UNUSABLE') LOOP
    EXECUTE IMMEDIATE 'ALTER INDEX ' || r.index_name || ' REBUILD';
  END LOOP;
END;
/
EOF

シナリオ3:パーティションメンテナンス

-- 月次パーティション追加
ALTER TABLE orders ADD PARTITION p_202606 
  VALUES LESS THAN (TO_DATE('2026-07-01', 'YYYY-MM-DD'));

-- 12ヶ月前のパーティション削除
ALTER TABLE orders DROP PARTITION p_202506;

-- Global index の対応
ALTER TABLE orders DROP PARTITION p_202506 UPDATE GLOBAL INDEXES;

-- または後で
ALTER INDEX orders_idx REBUILD;

シナリオ4:CA Clarity / EBS 対応

CA Clarity / Oracle EBS で ORA-01502 発生

1. DB バックアップ確認
2. アプリケーションサービス停止
3. UNUSABLE インデックス特定
4. REBUILD スクリプト実行
5. アプリケーションサービス再起動
6. 動作確認

-- 対象例:
ALTER INDEX NIKU.CMN_SEC_ASSGND_OBJ_PERM_PK REBUILD;

シナリオ5:CI/CD 統合

- name: Post-migration index check
  run: |
    sqlplus -s $DB_USER/$DB_PW <<EOF
    WHENEVER SQLERROR EXIT SQL.SQLCODE
    DECLARE
      v_count NUMBER;
    BEGIN
      SELECT COUNT(*) INTO v_count FROM user_indexes WHERE status = 'UNUSABLE';
      IF v_count > 0 THEN
        RAISE_APPLICATION_ERROR(-20001, 'Unusable indexes: ' || v_count);
      END IF;
    END;
    /
    EOF

Kamal 2 デプロイの詳細は Kamal 2 デプロイの記事を参照してください。

シナリオ6:Docker Oracle テスト

docker exec -it oracle-xe sqlplus scott/tiger <<EOF
CREATE TABLE t (id NUMBER PRIMARY KEY, val VARCHAR2(100));
CREATE INDEX idx_val ON t(val);

-- UNUSABLE に明示
ALTER INDEX idx_val UNUSABLE;

-- 状態確認
SELECT index_name, status FROM user_indexes;
-- IDX_VAL: UNUSABLE

-- SELECT
SELECT * FROM t WHERE val = 'test';
-- 通常は SKIP_UNUSABLE_INDEXES=TRUE でフルスキャン

-- ヒント強制
SELECT /*+ INDEX(t idx_val) */ * FROM t WHERE val = 'test';
-- ORA-01502

-- REBUILD
ALTER INDEX idx_val REBUILD;
EOF

Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。

シナリオ7:大量ロード最適化

-- 1. インデックス無効化(高速化)
ALTER INDEX orders_idx1 UNUSABLE;
ALTER INDEX orders_idx2 UNUSABLE;

-- 2. 大量 INSERT(Direct Path)
INSERT /*+ APPEND */ INTO orders SELECT * FROM staging;
COMMIT;

-- 3. REBUILD ONLINE
ALTER INDEX orders_idx1 REBUILD ONLINE PARALLEL 4;
ALTER INDEX orders_idx2 REBUILD ONLINE PARALLEL 4;

-- 4. 並列度リセット
ALTER INDEX orders_idx1 NOPARALLEL;

シナリオ8:Python 監視

import oracledb
import logging

def check_unusable_indexes(dsn, user, pw):
    with oracledb.connect(user=user, password=pw, dsn=dsn) as conn:
        cursor = conn.cursor()
        
        # 通常インデックス
        cursor.execute("""
            SELECT owner, index_name FROM dba_indexes 
            WHERE status = 'UNUSABLE'
              AND owner NOT IN ('SYS', 'SYSTEM')
        """)
        unusable = cursor.fetchall()
        
        # パーティション
        cursor.execute("""
            SELECT index_owner, index_name, partition_name 
            FROM dba_ind_partitions 
            WHERE status = 'UNUSABLE'
              AND index_owner NOT IN ('SYS', 'SYSTEM')
        """)
        unusable_parts = cursor.fetchall()
        
        if unusable or unusable_parts:
            logging.critical(f"Unusable indexes: {len(unusable)} + {len(unusable_parts)} partitions")
            return False
        return True

シナリオ9:Rails ヘルスチェック

class DatabaseHealthController < ApplicationController
  def index_check
    result = ActiveRecord::Base.connection.select_all(<<-SQL).to_a
      SELECT owner, index_name FROM dba_indexes 
      WHERE status = 'UNUSABLE'
        AND owner = '#{Rails.application.config.database_owner}'
    SQL
    
    if result.any?
      render json: { 
        status: 'CRITICAL',
        unusable_indexes: result 
      }, status: 503
    else
      render json: { status: 'OK' }
    end
  end
end

シナリオ10:定期監視スクリプト

#!/bin/bash
# monitor_indexes.sh

count=$(sqlplus -s / as sysdba <<EOF
SET HEADING OFF FEEDBACK OFF
SELECT COUNT(*) FROM dba_indexes WHERE status = 'UNUSABLE';
EXIT;
EOF
)

if [ "$count" -gt "0" ]; then
  echo "ALERT: $count unusable indexes" | mail -s "Oracle Index Alert" ops@example.com
fi

crontab の詳細は crontab 使い方の記事も参照してください。


トラブルシューティング

REBUILD が遅い

PARALLEL オプション or NOLOGGING(バックアップ必須):

ALTER INDEX big_idx REBUILD PARALLEL 8 NOLOGGING;

REBUILD で領域不足

別テーブルスペース:

ALTER INDEX big_idx REBUILD TABLESPACE new_ts;

PARTITION 数百個ある

PARALLEL 実行並列度制限でリソース管理。

SKIP_UNUSABLE_INDEXES 効かない

PRIMARY KEY / UNIQUE 制約のみは常に効果あり、制約チェック優先。

Rails migration で発生

UPDATE INDEXES clause で予防、必ずマイグレーション後検証。

PostgreSQL からの移行

PG は REINDEX、Oracle は ALTER INDEX REBUILD。オンライン対応の違いに注意。


よくある質問(FAQ)

Q1. REBUILD と REBUILD ONLINE の違い

  • REBUILD: 排他ロック、業務停止
  • REBUILD ONLINE: DML 継続可、若干負荷

Q2. PARTITION 単位の REBUILD

REBUILD PARTITION、Local index の一部のみ対応可。

Q3. SKIP_UNUSABLE_INDEXES の効果

オプティマイザ回避、PRIMARY KEY/UNIQUE の DML は不可のまま。

Q4. UPDATE INDEXES clause

DDL 時に自動再構築、UNUSABLE を予防。

Q5. NOLOGGING のリスク

バックアップ必須、REDO 生成せず高速だがリカバリ不能。

Q6. Global vs Local

  • Global: DDL で全体 UNUSABLE
  • Local: 該当パーティションのみ

Q7. Rails での対応

マイグレーション後検証、UPDATE INDEXES 使用。

Q8. Java での対応

errorCode == 1502 検出、DBA 通知。

Q9. Python での対応

oracledb.DatabaseError.code == 1502

Q10. パフォーマンス影響

インデックス UNUSABLE = SELECT 激低下、DML 停止も。

Q11. 予防策

  • UPDATE INDEXES clause
  • 大量ロード後の REBUILD 自動化
  • 監視スクリプト
  • CI/CD 検証

Q12. Autonomous DB での挙動

自動メンテナンスあるが、DDL 後は要確認。


参考リンク

Oracle 公式


まとめ

ORA-01502: index or partition of such index is in unusable state の要点を再整理します。

エラーの本質

インデックス or パーティションが UNUSABLE 状態
→ 使用不可、SELECT 激低下 or DML 停止
→ REBUILD で復旧
→ PRIMARY KEY/UNIQUE は特に影響大

エラーメッセージの読み方

ORA-01502: index 'SCHEMA.INDEX_NAME' or partition of such index 
           is in unusable state
                    ↑
             使用不可のインデックス

インデックス STATUS 階層

ビューSTATUS
DBA_INDEXESVALID / UNUSABLE / N/A
DBA_IND_PARTITIONSUSABLE / UNUSABLE / N/A
DBA_IND_SUBPARTITIONSUSABLE / UNUSABLE

10大原因

#原因対処
Direct Path InsertREBUILD
ALTER TABLE MOVEUPDATE INDEXES
パーティション DDLREBUILD PARTITION
UNUSABLE 明示REBUILD
Data Pump一括 REBUILD
REBUILD 中断再度 REBUILD
一意制約違反重複解消 + REBUILD
領域不足別 TS で REBUILD
IOT 再編成UPDATE INDEXES
Sysem crash再度 REBUILD

6つの解決策

-- ① 基本 REBUILD
ALTER INDEX idx_name REBUILD;

-- ② ONLINE(業務中)
ALTER INDEX idx_name REBUILD ONLINE;

-- ③ PARTITION 単位
ALTER INDEX idx_name REBUILD PARTITION p_202506;

-- ④ 一括スクリプト
BEGIN
  FOR r IN (SELECT owner, index_name FROM dba_indexes 
            WHERE status = 'UNUSABLE') LOOP
    EXECUTE IMMEDIATE 
      'ALTER INDEX ' || r.owner || '.' || r.index_name || ' REBUILD ONLINE';
  END LOOP;
END;

-- ⑤ SKIP_UNUSABLE_INDEXES(一時回避)
ALTER SESSION SET SKIP_UNUSABLE_INDEXES = TRUE;

-- ⑥ UPDATE INDEXES(予防)
ALTER TABLE orders MOVE TABLESPACE new_ts UPDATE INDEXES;

診断クエリ Top 3

-- ① 通常インデックス
SELECT owner, index_name FROM dba_indexes WHERE status = 'UNUSABLE';

-- ② パーティション
SELECT index_owner, index_name, partition_name 
FROM dba_ind_partitions WHERE status = 'UNUSABLE';

-- ③ REBUILD スクリプト生成
SELECT 'ALTER INDEX ' || owner || '.' || index_name || ' REBUILD ONLINE;'
FROM dba_indexes WHERE status = 'UNUSABLE';

予防のポイント

1. ALTER TABLE MOVE には UPDATE INDEXES
2. パーティション DDL には UPDATE GLOBAL INDEXES
3. Direct Path Insert 後は必ず REBUILD
4. 大量ロード時は UNUSABLE + REBUILD パターン
5. REBUILD ONLINE で業務停止最小化
6. 監視スクリプト設置
7. CI/CD で post-migration 検証
8. Rails マイグレーション後の検証
9. 定期メンテナンス(月次)
10. Local index 推奨(Global より影響小)

これらの知識は、Oracle DBA の本番運用・パフォーマンスチューニング・パーティション管理・データ移行・Rails / Java / Python アプリ運用・CI/CD パイプライン・大量データロード・CA Clarity / Oracle EBS 等の商用アプリ管理など、あらゆる場面で活用できます。本記事をブックマークしておけば、ORA-01502 に出会っても冷静に的確に対処できるようになります。


本記事は2026年6月時点の情報をもとに、Oracle Database 19c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。