据某客户反馈,其管理的某套数据库由于非正常关机重启之后,通过expdp进行数据导出发现报错,当报错之后,expdp数据导出命令直接终止退出,报错信息如下所示。
处理对象类型 SCHEMA_EXPORT/JOB . . 导出了 "STATS"."T_REPORT_MONTH_TEMPS" 988.2 MB 1292221 行 ORA-39014: 一个或多个 worker 进程已过早地退出。 ORA-39029: worker 进程 1 (进程名为 "DW01") 过早地终止 ORA-31672: Worker 进程 DW01 意外停止。
作业 "SYS"."SYS_EXPORT_SCHEMA_04" 因致命错误于 23:58:10 停止
此时我让客户检查数据库告警日志,发现有如下ORA-07445错误。
Errors in file /u01/app/oracle/admin/orcl/bdump/orcl_dw01_28608.trc: ORA-07445: 出现异常错误: 核心转储 [klufprd()+321] [SIGSEGV] [Address not mapped to object] [0x000000000] [] []
通过分析上面的信息,可以得到如下几个结论。
(1)expdp的写进程报错,因为日志产生的进程是dw01进程。
(2)dw进程报错的原因是遭遇了ora-07445 [klufprd()+321]错误。
(3)[klufprd()+321] 这个函数非常少见。但是从前面2点可以知道这肯定与buffer cache有关系。
客户咨询,有没有什么好的解决方法。实际上当想到上述几点之后,脑海中马上就出现了一个临时解决方案。就是,通过alter system flush buffer_cache 刷新缓存之后,然后再次进行expdp数据导出操作。
据客户反馈,在进行flush buffer_cache之后,再次运行expdp数据导出命令,发现expdp 操作仍然会报类似错误,但是expdp 不会异常终止,会继续完成后面其他对象的导出。通过临时解决方法,能实现这一点,基本上也算告一段落了,至少客户导出数据的目标是实现了。
然而对于我而言,作为一个好奇心很重的DBA,势必要弄清楚,Oracle这里为什么会发生ORA-07445错误。为什么expdp会异常终止退出,这些目前来看都是谜团。
进一步检查日志,发现有如下错误。
*** SESSION ID:(2760.1968) 2016-04-08 00:14:14.347 row 01808438.0 continuation at file# 6 block# 33784 slot 14 not found ************************************************** KDSTABN_GET: 0 ..... ntab: 1 curSlot: 14 ..... nrows: 14 ************************************************** *** 2016-04-08 00:14:14.348 ksedmp: internal or fatal error ORA-00600: internal error code, arguments: [kdsgrp1], [], [], [], [], [], [], [] Current SQL statement for this session: SELECT /*+NESTED_TABLE_GET_REFS+*/ "STATS"."T_REPORT_MONTH".* FROM "STATS"."T_REPORT_MONTH" ----- Call Stack Trace -----
擦亮眼睛发现,这里提到的这个表,恰好就是expdp报错所遇到的表,只不过我们在刷新buffer cache之后,expdp可以跳过这个表继续完成其他对象的导出。从上述的信息来看,这里确实存在一些错误。当然,客户也意识到了这样一点,习惯性的通过dbv 自带工具对该表所在的数据文件进行坏块检查,然而却并没有发现文件中存在数据坏块。
从这里的信息来看,Oracle发现所需要的这行记录row 01808438.0 应该在file 6 block 33784 中找到,但是却并没有发现。(注意,这里的file 6 block 33784 本身是完好的,因为dbv检测并没有发现坏块)。
那么这里的row 01808438.0 表示什么含义呢?
其实这是表示的nrid,这可以理解为一个指针。其中前面一部分是表现rdba地址,后面表现行编号。
那么针对这个问题,我们如何进一步进行分析呢?其实很简单,分别将block 33784以及rdba 01808438(16进制)进行dump,然后进行块级别分析。如下是rdba地址转换的脚本。
SQL> SELECT dbms_utility.data_block_address_block(25199672) "BLOCK", 2 dbms_utility.data_block_address_file(25199672) "FILE" 3 FROM dual; BLOCK FILE ---------- ---------- 33848 6
由于前面的报错日志中提到的是row 01808438.0 ,那么我们首先来分析file 6 block 33848的dump。
Block header dump: 0x01808438 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 0x000a.02d.000cdc5c 0x00809c91.6507.21 --U- 2 fsc 0x0001.78b6a4b1 0x02 0x000a.014.000cdd00 0x00806957.650d.15 --U- 2 fsc 0x0000.78b6ec5d 0x03 0x000a.025.000cdd5d 0x00801e50.650f.0a --U- 2 fsc 0x0000.78b71584 data_block_dump,data header at 0x1fb2f87c =============== tsiz: 0x1f80 hsiz: 0x34 pbl: 0x1fb2f87c bdba: 0x01808438 76543210 flag=-------- ntab=1 nrow=17 frre=-1 fsbo=0x34 fseo=0xd2 avsp=0x33b tosp=0x33c 0xe:pti[0] nrow=17 offs=0 0x12:pri[0] offs=0x1e34 ...... 0x30:pri[15] offs=0x6e2 0x32:pri[16] offs=0x583 block_row_dump: tab 0, row 0, @0x1e34 tl: 332 fb: --H-F--- lb: 0x0 cc: 79 nrid: 0x018083f8.e col 0: [ 5] c4 04 5a 27 1b col 1: [ 7] 47 59 30 32 30 30 31 col 2: [ 4] c3 15 11 04 col 3: [12] 31 38 37 33 34 34 32 30 30 30 30 36 col 4: [12] 31 34 30 34 34 34 32 30 30 30 30 31 col 5: [30] ……省略部分内容
上述的dump信息表示的是rdba 地址01808438的第0行,也就是我们大家所理解的第一行数据。我们可以发现在这行记录中,行头存在一个nrid地址(0x018083f8.e)。 既然这里提到nrid,那么我们就有必要来解释一下。当出现行迁移或者行链接时,数据块内用nrid来标示下一个rowid地址。
首先我们说一下Oracle的行迁移。行迁移分为几种情况,最常见的一种其实是数据块内的。一个数据块中单条记录的最大列数是255列,当一行记录的列超过255时,其他的列数据库会被Oracle 分成另外一个row piece存在同一个数据块中(当然也有可能存到其他数据块)。也就是说超过255列的行数据,会被分成多个row piece。而当我们读取这个行数据时,怎么知道是一个完整的整体呢?答案就是nrid。Oracle 通过nrid来将这多个row piece 串在一起,组成一个完整的行数据。另外其中常见情况就是更新时,数据块内剩余空间不足容纳更新之后的列数据时,也会产生行迁移。
而行链接通常是一个数据块不足以容纳一条数据,需要申请多个数据块来容纳一条记录,而这种情况通常都是lob的使用场景下才会出现。正因为如此,大家所见的场景通常其实都是行迁移,而非行链接。
想到这一点,那么我们再回头去看下前面的错误。row 01808438.0 表示这个数据块的第0行,而该数据块的第0行所存在的nrid地址是0x018083f8.e; 那么我们进一步到0x018083f8数据块中去寻找第e行记录,却发现结果是这样的。
Object id on Block? Y 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 0x000a.013.000cdc01 0x01c02834.6573.33 --U- 2 fsc 0x0000.78cbf31d 0x02 0x000a.001.000cda7a 0x0080150a.64d3.21 C--- 0 scn 0x0b37.78b584df 0x03 0x000a.01e.000cdade 0x00801510.64d3.13 C-U- 0 scn 0x0b37.78b99f21 data_block_dump,data header at 0x2b4fc709007c =============== tsiz: 0x1f80 hsiz: 0x2e pbl: 0x2b4fc709007c bdba: 0x018083f80x018083f8 76543210 flag=-------- ntab=1 nrow=14 frre=-1 fsbo=0x2e fseo=0x568 avsp=0x53a tosp=0x53a 0xe:pti[0] nrow=14 offs=0 0x12:pri[0] offs=0x1d78 0x14:pri[1] offs=0x1c37 ...... 0x2a:pri[12] offs=0x6c2 0x2c:pri[13] offs=0x568 block_row_dump: tab 0, row 0, @0x1d78 tl: 520 fb: -----L-- lb: 0x0 cc: 255 ......省略部分内容 tab 0, row 13, @0x568 tl: 346 fb: --H-F--- lb: 0x1 cc: 79 nrid: 0x018083f8.c col 0: [ 5] c4 04 5a 3a 0a col 1: [ 7] 47 59 30 32 30 30 31 col 2: [ 4] c3 15 11 04 ...... col 76: [ 1] 80 col 77: [ 1] 80 col 78: [ 1] 80 end_of_block_dump End dump data blocks tsn: 6 file#: 6 minblk 33784 maxblk 33784
可以发现,我们定位到0x018083f8数据块的第e行(也就是第14行),其实就是该数据块的最后一行数据。然而我们发现该行数据也存在一个nrid。该nrid是0x018083f8.c,这表示该33784数据块第12行记录。和第13行是组合成一条完整行记录的。
换句话说,我们前面报错的那条记录,应该有2个row piece,其中一个row piece 是存在的,其中一个row piece 本应该存在在33784 数据块中。但是存在33784中的这个row piece的nrid 是指向另外一行记录。很明显这是不匹配的。
正是因为如此,由于对应的row piece不正确,Oracle才报了上述的错误。
实际上遇到该错误之后,大多数人都会以为是索引的问题,通过drop 重建可以解决,然而这里的问题比较特殊。本质上是表自身的某条数据有问题, 所以这就是为什么客户重建索引会报错的原因。
SQL> CREATE INDEX "STATS"."MONTHINDEX_STATUS2" ON "STATS"."T_REPORT_MONTH" ("TARGET_298", "UNIT_LEVEL", "TARGET_VAL", "MONTH_FLG") 2 TABLESPACE "STATDATA" ; 第 1 行出现错误: ORA-00600: 内部错误代码, 参数: [kdsgrp1], [], [], [], [], [], [], []
最后我们清楚原因之后就可以很好解决这个问题了。通过rowid的方式跳过这行有问题的记录,将其他数据取出,重建表即可。