某客户说一套数据库由于非正常关机重启之后,进行数据导出发现报错,expdp无法正常工作,报错之后直接退出:
1234567 | 处理对象类型 . ORA-39014: ORA-39029: ORA-31672: 作业 |
而检查此时的alert log可以发现有如下类似的错误:
12 | Errors ORA-07445: |
从上面的信息我们可以得到如下几个结论:
1、expdp的写进程报错,因为日志产生是dw进程。
2、dw进程报错的原因是遭遇了ora-07445 [klufprd()+321]错误。
3、对于[klufprd()+321] 这个函数,非常少见。但是从前面2点我们可以知道这肯定与buffer cache有关系。
所以要临时解决这个问题也很简单。通过alter system flush buffer_cache 刷新缓存之后,再次进行expdp操作.
后续客户尝试了之后,发现expdp 操作虽然仍然会报错,但是expdp 不会异常终止,会继续完成后面其他对象的导出。
进一步分析报错的信息可以看到,有如下这样的提示:
*** row 01808438.0 continuation at file# 6 block# 33784 slot 14 not found ************************************************** KDSTABN_GET: curSlot: ************************************************** *** ksedmp: ORA-00600: Current SELECT ----- |
很明显,这里提到这个这个表,恰好就是expdp报错所遇到的表,只不过我们刷新buffer cache之后,expdp可以跳过这个表继续完成其他对象的导出。
从上述的信息来看,这里存在错误。客户也意识到,通过dbv 对数据文件进行检查,但是发现文件并没有损坏。
这里我们要注意,dbv 同时是检查物理坏块,对于逻辑坏块通常无能为力,当然块内的逻辑错误,这类型的块dbv是可以检查出来的。
但是从这里的信息来看,Oracle发现所需要的这行记录row 01808438.0 应该在file 6 block 33784 中找到,但是却并没有发现。
注意,这里的file 6 block 33784 本身是完好的。
那么这里的row 01808438.0 表示什么含义呢?
其实这是表示的nrid;这可以理解为一直指针;其中前面一部分是表现rdba地址,后面表现行编号。
如果要进一步分析为什么会这个错误,我们怎么办呢? 很简单,分别将block 33784以及rdba 01808438(16进制) 进行dump。 如下是转换的脚本:
1234567 | SQL> 2 dbms_utility.data_block_address_file(25199672) "FILE" 3 FROM dual; BLOCK FILE ---------- 33848 6 |
日志报错中提到的是row 01808438.0 ,那么我们首先来分析file 6 block 33848的dump:
123456789101112131415161718192021222324252627282930313233343536373839404142 | Block Object id on Block? Y seg/obj: 0xc03d01 csc: 0xb37.78b5ae28 itc: 3 flg: E typ: 1 - DATA brn: 0 bdba: 0x1807d8a ver: 0x01 opc: 0 inc: 0 exflg: 0 Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x02 0x03 data_block_dump,data =============== tsiz: hsiz: pbl: bdba: 76543210 flag=-------- ntab=1 nrow=17 frre=-1 fsbo=0x34 fseo=0xd2 avsp=0x33b tosp=0x33c 0xe:pti[0] 0x12:pri[0] ...... 0x30:pri[15] 0x32:pri[16] block_row_dump: tab tl: nrid: col col col col col col ...... |
上述类似表示的是rdba 地址01808438的第0行,也就是我们大家所理解的第一行。我们可以发现这行记录中,行头存在一个nrid地址。
说到nrid地址,这通常是针对行链接,行迁移才会遇到的一种情况。那么这里为什么会出现呢?
行迁移几种,最常见的一种其实是block内的。一个block中单条记录的最大列数是255列,当一行记录的列超过255时,其他的列数据库会被oracle 分成另外一个row piece存在同一个block中(当然也有可能存到其他block)。
也就是说超过255列的行数据,会被分成多个row piece;而当我们读取这个行数据时,怎么知道是一个完整的整体呢?
答案就是nrid,oracle 通过nrid来将这多个row piece 串在一起,组成一个完整的行数据。
想到这一点,那么我们再回头去看下前面的错误。row 01808438.0 表示这个block的第0行,而该block的第0行所存在的nrid地址是:0x018083f8.e
那么我们进一步到block 0x018083f8中去寻找第e行记录,发现结果是这样的:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 | Object seg/obj: 0xc03d01 csc: 0xb37.78bb5e9f itc: 3 flg: E typ: 1 - DATA brn: 0 bdba: 0x1807d8a ver: 0x01 opc: 0 inc: 0 exflg: 0 Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x02 0x03 data_block_dump,data =============== tsiz: hsiz: pbl: bdba: 76543210 flag=-------- ntab=1 nrow=14 frre=-1 fsbo=0x2e fseo=0x568 avsp=0x53a tosp=0x53a 0xe:pti[0] 0x12:pri[0] 0x14:pri[1] ...... 0x2a:pri[12] 0x2c:pri[13] block_row_dump: tab tl: ...... tab tl: nrid: col col col ...... col col col end_of_block_dump End |
我们可以看到这里对应的记录根本就没有。因为该block最后一条记录是row 13,也就是第14行,也是一个row piece,而且存在一个nrid。
该nrid是0x018083f8.c,这表示该block 33784第12行记录。跟row 13是组合成一条完整行记录的。
换句话说,我们前面报错的那条记录,应该有2个row piece,其中一个row piece 是存在的,其中一个row piece 本应该存在在33784 block中。
但是由于找不到该row piece,因此oracle报了上述的错误。
实际上该错误遇到之后,我们通常以为是index的问题,通过drop 重建可以解决,然而这里的问题比较特殊,据说是表的数据有问题。
所以这就是为什么客户重建index会报错的原因:
123456 | SQL> 2 TABLESPACE "STATDATA" ; CREATE * 第 ORA-00600: 最后,我们清楚了所有原因,那么要解决该问题很简单。通过rowid的方式跳过这行有问题的记录,将其他数据取出,重建表即可。 |
备注:关于块内的行迁移问题,可以参考之前的文章 http://www.killdb.com/2013/06/19/intra-blcok-chain.html