ORA-08102 "index key not found" 是 DBA 面對核心資料字典損壞時最令人心驚膽戰的錯誤之一。本文記錄一個真實生產環境案例:OBJ$ 表與其唯一索引 I_OBJ4 出現鍵值不一致,導致 CREATE TABLE 等 DDL 全面失效。從現象確認、Root Cause 分析、BBED 修改資料塊、標記壞塊嘗試,到最終採用 Bootstrap$ 核心索引重建策略完美恢復,完整呈現核心字典損壞的診斷邏輯與極限修復技術。
SQL> startup
ORACLE instance started.
Database mounted.
Database opened.
SQL> create table t1 as select * from dual;
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-08102: index key not found, obj# 39, file 1, block 93842 (2)
obj# 39 對應 I_OBJ4 索引(OBJ$ 表上 (DATAOBJ#, TYPE#, OWNER#) 的唯一索引)。任何需要分配新 OBJECT_ID 的操作(建表、建索引、建序列等)都會遞歸查詢 OBJ$,進而觸發該錯誤,導致資料庫寫入功能癱瘓。
-- 索引中有但表中無
SQL> select /*+ index(t i_obj4) */ DATAOBJ#,TYPE#,OWNER# from obj$ t
minus
select /*+ full(t1) */ DATAOBJ#,TYPE#,OWNER# from obj$ t1;
DATAOBJ# TYPE# OWNER#
---------- ---------- ----------
97109 0 0
-- 表中有但索引中無
SQL> select /*+ full(t1) */ DATAOBJ#,TYPE#,OWNER# from obj$ t1
minus
select /*+ index(t i_obj4) */ DATAOBJ#,TYPE#,OWNER# from obj$ t;
DATAOBJ# TYPE# OWNER#
---------- ---------- ----------
97094 0 0
進一步定位到具體 Rowid:
SQL> select /*+ index(t i_obj4) */ rowid,DATAOBJ#,TYPE#,OWNER# from obj$ t
minus
select /*+ full(t1) */ rowid,DATAOBJ#,TYPE#,OWNER# from obj$ t1;
ROWID DATAOBJ# TYPE# OWNER#
------------------ ---------- ---------- ----------
AAAAASAABAAAADxAAb 97109 0 0
SQL> select name,obj#,dataobj# from obj$ where rowid='AAAAASAABAAAADxAAb';
NAME OBJ# DATAOBJ#
------------- ------ ----------
_NEXT_OBJECT 1 97094
結論:同一 Rowid 在表中 DATAOBJ#=97094,在索引 I_OBJ4 中卻為 97109,鍵值不一致導致 ORA-08102。
嘗試將表中該行 DATAOBJ# 從 97094 改為 97109,使雙端一致:
-- 確認資料塊位置
SQL> select dbms_rowid.rowid_relative_fno(rowid) file#,
dbms_rowid.rowid_block_number(rowid) block#,
dbms_rowid.rowid_row_number(rowid) row#
from obj$ where rowid='AAAAASAABAAAADxAAb';
FILE# BLOCK# ROW#
---------- ---------- ----------
1 241 27
-- DUMP 對比
SQL> select dump(97109,16) from dual; -- Typ=2 Len=4: c3,a,48,a
SQL> select dump(97094,16) from dual; -- Typ=2 Len=4: c3,a,47,5f
使用 BBED 進入 system01.dbf Block 241,Offset 389(DATAOBJ# 列起始位置),將 47 5f 修改為 48 0a,sum apply 校驗通過。
SQL> select /*+ index(t i_obj4) */ rowid,DATAOBJ#,TYPE#,OWNER# from obj$ t
minus
select /*+ full(t1) */ rowid,DATAOBJ#,TYPE#,OWNER# from obj$ t1;
no rows selected
SQL> select /*+ full(t1) */ rowid,DATAOBJ#,TYPE#,OWNER# from obj$ t1
minus
select /*+ index(t i_obj4) */ rowid,DATAOBJ#,TYPE#,OWNER# from obj$ t;
no rows selected
雙向 MINUS 均無差異,但 CREATE TABLE 仍報 ORA-08102,且 ANALYZE TABLE sys.obj$ VALIDATE STRUCTURE CASCADE 拋出 ORA-01499: table/index cross reference failure。
啟用 Event 802 Trace:
SQL> ALTER SESSION SET EVENTS '802 trace name errorstack level 3';
SQL> create table t as select * from dual;
...
oer 8102.2 - obj# 39, rdba: 0x00416e92(afn 1, blk# 93842)
kdk key 8102.2:
ncol: 4, len: 16
key: (16): 04 c3 0a 48 0a 01 80 01 80 06 00 40 00 f1 00 1b
Trace 確認錯誤仍發生在 I_OBJ4 Block 93842 上,鍵值包含 97109 (c3 0a 48 0a)。
對比 MAX(DATAOBJ#):
SQL> select max(dataobj#) from obj$;
MAX(DATAOBJ#)
-------------
96815
-- 執行計畫顯示 INDEX FULL SCAN (MIN/MAX) on I_OBJ4
-- 說明索引葉塊鏈表已斷裂,MIN/MAX 掃描無法到達真實最大值
核心判斷:I_OBJ4 索引結構內部鏈表損壞,雖然葉塊記錄與表數據已通過 BBED 對齊,但索引結構元數據(Branch 塊指針、Leaf 塊雙向鏈表)仍指向舊鍵值位置,導致遞歸 SQL 遍歷時仍讀到不存在的鍵。
嘗試用 BBED 修改 Block 93842 Tail Checksum 標記為 Fractured Block,並設定 _corrupted_rollback_segments/Event 10231 跳過壞塊:
BBED> set block 93842
BBED> set offset 8188
BBED> m /x 02
BBED> sum apply
結果:錯誤從 ORA-08102 轉為 ORA-01578: ORACLE data block corrupted,無法繞過。證明核心字典索引損壞無法通過壞塊屏蔽解決。
由於 I_OBJ4 屬於 Bootstrap$ 階段建立的核心索引(I_OBJ1, I_USER1, I_FILE#_BLOCK#, I_IND1, I_TS#, I_CDEF1...),無法在資料庫 Open 狀態下 DROP/REBUILD。採用如下方案:
I_OBJ4 字典記錄:在 BOOTSTRAP$ 模式下(STARTUP UPGRADE 或 _allow_resetlogs_corruption=TRUE),從 OBJ$、IND$、ICOL$、SEG$、TS$ 等基表手工刪除 I_OBJ4 相關元數據行。sql.bsq / catalog.sql 中 I_OBJ4 建立腳本,手工執行 CREATE UNIQUE INDEX I_OBJ4 ON OBJ$(DATAOBJ#, TYPE#, OWNER#) ...。ANALYZE TABLE sys.obj$ VALIDATE STRUCTURE CASCADE 通過,CREATE TABLE 正常,業務全面恢復。OBJ$、USER$、TS$、FILE$、IND$、ICOL$、SEG$、BOOTSTRAP$ 任何一張表或其索引損壞,都可能導致資料庫無法 Open 或 DDL 癱瘓。BACKUP DATABASE INCLUDE CURRENT CONTROLFILE SPFILE + 定期 EXPDP FULL=Y + FLASHBACK DATABASE 開啟。本案例再次證明:資料庫恢復的場景不可大意,客戶的故障只有你想不到的,沒有遇不到的。敬畏數據,備份先行。