↓ 跳过正文
  1. Posts/

Oracle 11.2.0.4 Data Pump 恢复 T_S_BIZ_LOG 时 ORA-07445:一次 klxprParseRow 崩溃排查记录

Oracle 11.2.0.4 Data Pump 恢复 T_S_BIZ_LOG 时 ORA-07445:一次 klxprParseRow 崩溃排查记录
#

最近处理一次 Oracle 11.2.0.4 数据库迁移。

源库使用 ASM,目标库是普通单机文件系统,需要通过 Data Pump 将源库用户 gzdb 恢复到测试库的 gzdb。

表空间同时进行:

USERS → YSSUCO

整体恢复过程中,大部分对象都可以正常导入。

但是有一张表始终非常奇怪:

T_S_BIZ_LOG

只要恢复到这张表的数据阶段,Data Pump Worker 就可能直接崩溃。

不是普通的:

ORA-39083
ORA-31684
ORA-39151

而是数据库后台进程直接出现:

ORA-07445
SIGSEGV

最终定位到:

klxprParseRow()

这次排查比较有意思,因为后来在 Oracle 后续版本的 Bug Fix 列表中,发现了一个与这个函数完全对应的 Bug:

31670014
AIM ORA-7445 [KLXPRPARSEROW] - KLXPRPARSEROW

因此把整个过程记录下来。


一、环境
#

本次环境:

Oracle Database 11.2.0.4.0
Linux x86_64
64-bit

源库:
gzdb

目标库:
gzdb

源库:
ASM

目标库:
单机文件系统

源库通过:

expdp "/ as sysdba" \
cluster=N \
dumpfile=gzdb_full-${vdate}.dmp \
full=y \
parallel=4 \
logfile=gzdb_full-${vdate}.log \
directory=expdpbak \
compression=all

生成全库 Data Pump 文件。

本次实际使用:

gzdb_full-2026-09-17.dmp

目标库导入:

gzdb
        ↓
gzdb

并进行:

USERS
  ↓
YSSUCO

表空间映射。


二、最开始怀疑是不是普通的 Data Pump 导入问题
#

最开始执行:

impdp "'/ as sysdba'" \
directory=BACKUP1 \
dumpfile=gzdb_full-2026-09-17.dmp \
logfile=20260922-1119.log \
schemas=gzdb \
remap_schema=gzdb:gzdb \
remap_tablespace=USERS:YSSUCO \
parallel=16 \
cluster=N \
exclude=STATISTICS

过程中出现了很多看起来比较吓人的信息:

ORA-31684 USER already exists
ORA-39151
ORA-39083
ORA-02304

其中最明显的是 TYPE 创建失败:

ORA-39083: Object type TYPE failed to create
ORA-02304: invalid object identifier literal

一开始很容易把这些错误和后面的表恢复失败联系起来。

实际上后来发现,这两类问题应该分开看。


三、ORA-02304:TYPE OID 问题
#

源库有一批用户自定义 TYPE。

例如:

STR_SPLIT
MAP_OBJECT_N2N
MAP_OBJECT
FEE_ROW_TYPE
ROW_TYPE
OBJ_TABLE
PORT_LIST_TYPE
DAE_ROW_TYPE
gz_CONCAT_IM
MAP_OBJECT_CLOB
OBJ_PARAMS
SPLITTYPE
VARTABLETYPE2
VARTABLETYPE
T_MAP_OBJECT_N2N
T_MAP_OBJECT
T_FEE_TYPE
T_MAP_OBJECT_CLOB
TAB_PARAMS

Data Pump 导入的时候生成类似:

CREATE TYPE ...
OID '2A7B2DF7A6C13042E0630408A8C066D4'
...

目标数据库直接执行这种 OID 创建:

CREATE TYPE TEST_OID
OID '2A7B2DF7A6C13042E0630408A8C066D4'
AS TABLE OF VARCHAR2(4000);

返回:

ORA-02304
invalid object identifier literal

后来通过 SQLFILE 把 TYPE DDL 提取出来,并去掉:

OID '...'

之后 19 个 TYPE 都可以正常创建。

这说明:

TYPE OID 问题

确实存在。

但是它和后面的:

klxprParseRow()
SIGSEGV

并不是同一个问题。


四、真正让 Data Pump 崩溃的是 T_S_BIZ_LOG
#

真正奇怪的是:

Data Pump 并不是简单报告某一行数据错误。

而是 Worker 进程直接崩了。

trace 文件:

orcl_dw00_15519.trc

其中最关键的信息:

Oracle Database 11.2.0.4.0

以及:

DW00
Data Pump Worker

最终异常:

Exception [type: SIGSEGV, SI_KERNEL(general_protection)]
[ADDR:0x0]
[PC:0x1F22D68, klxprParseRow()+4516]

ORA-07445: exception encountered:
core dump [klxprParseRow()+4516]
[SIGSEGV]

这就不是普通 SQL 错误了。

这是 Oracle Server 进程自身访问了非法内存。


五、第一次看到的时候,很容易怀疑并行度
#

当时 Data Pump 使用:

PARALLEL=16

所以第一反应自然是:

是不是并行太高,把 Oracle 11.2.0.4 搞崩了?

于是继续降低并行。

甚至针对这张表单独执行:

impdp "'/ as sysdba'" \
directory=BACKUP1 \
dumpfile=gzdb_full-2026-09-17.dmp \
logfile=t_s_biz_log_restore_20260922.log \
tables=gzdb.T_S_BIZ_LOG \
remap_schema=gzdb:gzdb \
remap_tablespace=USERS:YSSUCO \
parallel=1 \
cluster=N

重点来了:

PARALLEL=1

还是崩。

而且仍然是:

klxprParseRow()

这一步基本排除了:

PARALLEL=16

是主要原因。


六、Data Pump 实际执行到了什么地方?
#

trace 中可以看到类似:

INSERT /*+ APPEND PARALLEL("T_S_BIZ_LOG",1)+*/
INTO RELATIONAL("gzdb"."T_S_BIZ_LOG" NOT XMLTYPE)
...
SELECT ...
FROM "ET$0CEDD59B0001" KU$

并且 SQL 中包含:

C_LOG_DETAIL
C_PLAIN_CONTENT

这两个字段。

也就是说,真正发生崩溃的位置,是:

Data Pump
    ↓
TABLE_DATA
    ↓
Data Pump Worker
    ↓
直接装载数据
    ↓
T_S_BIZ_LOG
    ↓
klxprParseRow()
    ↓
SIGSEGV
    ↓
ORA-07445

而不是:

建表失败

也不是:

建索引失败

更不是:

表空间不存在

七、T_S_BIZ_LOG 到底是什么表?
#

这张表本身并不是普通的小表。

大约:

2,260,121 rows

两个 CLOB:

C_LOG_DETAIL
C_PLAIN_CONTENT

其中:

C_LOG_DETAIL
最大长度约 655 KB

C_PLAIN_CONTENT
最大长度约 7.6 MB

源库统计:

C_LOG_DETAIL 非 NULL:
2,224,651

C_PLAIN_CONTENT 非 NULL:
2,221,263

其中:

C_PLAIN_CONTENT > 1 MB

大约:

990 rows

甚至发现:

C_PLAIN_CONTENT > 7 MB

还有:

53 rows

例如:

C_IDEN       LOG_LEN    PLAIN_LEN
4189815      627198     7604238
4189807      627198     7604238
4189836      627096     7604179
4189889      626912     7604088

因此这张表的特点非常明显:

220 万+
+
两个 BASICFILE CLOB
+
大量大 CLOB
+
单行 CLOB 最大约 7.6 MB

这和普通业务表完全不是一个级别的数据结构。


八、是不是 CLOB 本身坏了?
#

这是当时非常重要的一个问题。

检查:

SELECT COUNT(*)
FROM gzdb.T_S_BIZ_LOG;

结果:

2260121

同时检查:

SELECT
    MAX(DBMS_LOB.GETLENGTH(C_LOG_DETAIL)),
    MAX(DBMS_LOB.GETLENGTH(C_PLAIN_CONTENT))
FROM gzdb.T_S_BIZ_LOG;

得到:

C_LOG_DETAIL
655438

C_PLAIN_CONTENT
7604238

没有直接发现明显的 LOB 损坏证据。

而且普通 SQL 可以读取这些数据。

所以不能简单得出:

CLOB 数据坏了。

更准确的说法应该是:

这张表的数据特征触发了 Oracle 11.2.0.4 Data Pump 数据装载路径中的异常。


九、最关键的实验:把 T_S_BIZ_LOG 排除
#

后来做了一个非常重要的验证。

Data Pump 正常恢复整个 Schema,但是:

EXCLUDE=T_S_BIZ_LOG

也就是:

其他所有对象继续恢复
T_S_BIZ_LOG 暂时不恢复

使用 parfile:

DIRECTORY=BACKUP1
DUMPFILE=gzdb_full-2026-09-17.dmp
LOGFILE=gzdb_without_bizlog_20260922.log
SCHEMAS=gzdb
REMAP_SCHEMA=gzdb:gzdb
REMAP_TABLESPACE=USERS:YSSUCO
PARALLEL=16
CLUSTER=N
EXCLUDE=TABLE:"IN ('T_S_BIZ_LOG')"

结果:

完整 Schema 恢复完成

没有再发生:

DW00 SIGSEGV
ORA-07445
klxprParseRow()

这就是整个排查过程中最有价值的一次 A/B Test。


十、因此可以把问题范围缩到非常小
#

整个过程形成了一个非常清晰的矩阵:

测试结果
整个 Schema 导入失败
排除 T_S_BIZ_LOG成功
单独导入 T_S_BIZ_LOG失败
PARALLEL=16失败
PARALLEL=1仍然失败
普通 SQL 查询 T_S_BIZ_LOG正常
其他表 Data Pump正常
TYPE 手工创建可以绕过 OID 问题
删除 T_S_BIZ_LOG 后重新导入其他对象成功

所以基本可以确定:

不是整个 Dump 文件坏了
不是整个 Schema 坏了
不是简单的表空间不足
不是单纯 PARALLEL=16
不是普通用户权限问题
不是所有 CLOB 都无法恢复

而是:

T_S_BIZ_LOG
        ↓
特定数据 + CLOB
        ↓
Data Pump TABLE_DATA
        ↓
Oracle 11.2.0.4 内部处理
        ↓
klxprParseRow()
        ↓
SIGSEGV

十一、然后搜到了一个非常有意思的 Oracle Bug
#

这一步是整个案例最值得记录的地方。

继续搜索:

klxprParseRow Oracle

发现 Oracle 后续版本的 Bug Fix 列表中出现:

31670014
AIM ORA-7445 [KLXPRPARSEROW] - KLXPRPARSEROW

Oracle 19.10 RU 的 Bug Fix 列表中明确列出了:

31670014 | AIM ORA-7445 [KLXPRPARSEROW] - KLXPRPARSEROW

这与我们实际 trace 中:

ORA-07445
[klxprParseRow()+4516]

函数名称完全对应。(DBA - Rodrigo Jorge)

这个发现非常重要。


十二、但是不能简单说“就是 Bug 31670014”
#

这里必须谨慎。

目前公开资料能够证明:

Oracle 后续版本存在 Bug 31670014

并且:

Bug 31670014

的描述就是:

AIM ORA-7445 [KLXPRPARSEROW]

同时我们的:

Oracle 11.2.0.4

trace:

ORA-07445 [klxprParseRow()+4516]

完全对应函数。

但是:

31670014

是否直接 backport 到某个 11.2.0.4 PSU/BP/RU?

以及:

本案例具体是否就是 31670014

仅凭公开 Bug Fix 列表还不能 100% 下结论。

Oracle 内部 Bug 资料很多需要 MOS 权限才能看到完整:

Cause
Affected Versions
Fixed Versions
Patch
Backport
Workaround

因此本文更严谨的表述应该是:

本次事故与 Oracle 后续公开 Bug Fix 列表中的 Bug 31670014(KLXPRPARSEROW / ORA-7445)具有高度一致的函数级特征,但目前无法仅凭公开资料确认该 11.2.0.4 环境中的问题就是该 Bug 的 backport 版本。

这样记录比较靠谱。


十三、以前也有人遇到 Data Pump + ORA-07445
#

这也不是第一次有人遇到:

impdp
+
ORA-07445

Oracle 官方社区可以找到类似案例。

例如一个 2015 年的案例:

source: 11.2.0.4
target: 11.2.0.1

使用 impdp 恢复过程中直接出现:

ORA-07445

并且是在 Data Pump TABLE_DATA 处理过程中发生。(Oracle Forums)

另外更早的 Oracle 社区案例也有:

Oracle 11.2.0.2
Data Pump restore
ORA-07445

最终向 Oracle Support 提交 SR 后,建议升级/打补丁,或者绕开 Data Pump。(Oracle Forums)

这说明:

Data Pump
+
Oracle 11g
+
ORA-07445

本身并不是一个完全没有先例的组合。


十四、Oracle 官方对 ORA-07445 的定义
#

Oracle 自己对 ORA-07445 的解释也很明确。

ORA-07445 本质是:

Operating System Exception

也就是 Oracle Server 进程在执行内部代码时触发了操作系统级异常。

常见原因包括:

Bad Data
Misconfiguration
Oracle Bug

Oracle 建议通过:

alert.log
trace
ADR
ORA-07445 diagnostic information

进行定位。(Oracle Blogs)

所以遇到这种错误,最重要的一件事情不是继续不停地改:

PARALLEL=16
PARALLEL=8
PARALLEL=4
PARALLEL=1

而是:

看 trace

尤其是:

ORA-07445
[函数名()+偏移]

这一部分。


十五、为什么普通的 Data Pump 日志反而不容易看出问题?
#

这是这次事故非常容易误判的地方。

Data Pump log 最后可能只显示:

Processing object type ...

或者:

import job stopped due to fatal error

甚至没有明确告诉你:

哪一行数据

真正有价值的信息反而在:

$ORACLE_BASE/diag/rdbms/.../trace/

例如本次:

orcl_dw00_15519.trc

里面才出现:

DW00
Data Pump Worker

以及:

klxprParseRow()

和:

ORA-07445
SIGSEGV

所以:

Data Pump log 只能告诉你“恢复失败了”,trace 才告诉你“Oracle 自己崩在哪里”。


十六、为什么 T_S_BIZ_LOG 特别值得怀疑?
#

这张表有几个非常特殊的特征:

220 万+ 行

两个:

CLOB

而且:

C_PLAIN_CONTENT

单行最大:

7,604,238

约:

7.25 MB

另外还有接近:

1000

行超过:

1 MB

Oracle 11g Data Pump 对 LOB 数据处理本身就比普通 VARCHAR2 数据复杂。

Oracle 11g Data Pump 文档也专门提供了与 XML/CLOB 数据相关的 Data Pump 数据选项;另外公开资料中也有专门针对 Data Pump LOB 导入/导出性能和处理路径的诊断资料。(Oracle Docs)

因此从工程角度看:

T_S_BIZ_LOG
+
大 CLOB
+
11.2.0.4
+
Data Pump TABLE_DATA
+
klxprParseRow

确实是一个非常值得重点怀疑的组合。

但仍然不能简单等价成:

CLOB = 一定导致 Bug

目前没有足够证据这么下结论。


十七、最终恢复策略
#

既然已经证明:

其他 9850+ 张表可以正常恢复

最合理的方案就是:

Data Pump
       ↓
完整恢复 Schema
       ↓
排除 T_S_BIZ_LOG
       ↓
T_S_BIZ_LOG 单独处理

也就是:

恢复结构
+
恢复其他对象
+
T_S_BIZ_LOG 数据单独搬运

十八、T_S_BIZ_LOG 不一定非要再用 Data Pump
#

这张表完全可以采用:

Database Link
+
普通 INSERT

例如:

CREATE DATABASE LINK SRC_gzdb
CONNECT TO gzdb
IDENTIFIED BY "源库密码"
USING '源库TNS服务名';

然后:

SELECT COUNT(*)
FROM gzdb.T_S_BIZ_LOG@SRC_gzdb;

确认 DB Link 正常。

再进行分批:

INSERT INTO gzdb.T_S_BIZ_LOG
(
    C_IDEN,
    C_FUN_CODE,
    C_IDF_CODE,
    D_TRADE,
    C_PORT_CODE,
    C_ITEM_CODE,
    C_ITEM_NAME,
    C_RESULT,
    D_UPDATE,
    C_UPDATE_BY,
    C_UPDATE_TIME,
    C_LOG_DETAIL,
    C_PLAIN_CONTENT,
    C_FUNRELA_ID,
    C_DISPATCH_ID,
    C_START_TIME,
    C_END_TIME,
    C_MES_TEXT,
    C_TASK_CATEGORY,
    C_PROCESS_INST_ID,
    C_TASK_INST_ID,
    C_EXTRA_INFO,
    C_IP,
    C_USE_TIME,
    C_PLAN_CODE,
    C_ITEM_EXTRA_INFO,
    C_NODATA_TYPE
)
SELECT
    C_IDEN,
    C_FUN_CODE,
    C_IDF_CODE,
    D_TRADE,
    C_PORT_CODE,
    C_ITEM_CODE,
    C_ITEM_NAME,
    C_RESULT,
    D_UPDATE,
    C_UPDATE_BY,
    C_UPDATE_TIME,
    C_LOG_DETAIL,
    C_PLAIN_CONTENT,
    C_FUNRELA_ID,
    C_DISPATCH_ID,
    C_START_TIME,
    C_END_TIME,
    C_MES_TEXT,
    C_TASK_CATEGORY,
    C_PROCESS_INST_ID,
    C_TASK_INST_ID,
    C_EXTRA_INFO,
    C_IP,
    C_USE_TIME,
    C_PLAN_CODE,
    C_ITEM_EXTRA_INFO,
    C_NODATA_TYPE
FROM gzdb.T_S_BIZ_LOG@SRC_gzdb
WHERE ORA_HASH(C_IDEN, 19) = 0;

COMMIT;

然后:

0
1
2
...
19

分 20 批。

如果单批仍然压力比较大,可以直接改成:

0 ~ 39

也就是 40 批。


十九、为什么 DB Link + INSERT 值得尝试?#

因为它和 Data Pump 当前失败的路径不是完全相同的。

Data Pump 当前走的是:

Data Pump Worker
        ↓
TABLE_DATA
        ↓
direct path
        ↓
APPEND
        ↓
klxprParseRow()
        ↓
SIGSEGV

而普通:

INSERT INTO ...
SELECT ...
FROM ...@DBLINK;

属于普通 SQL 数据迁移路径。

所以可以把问题从:

Oracle Data Pump 内部数据装载

切换成:

Oracle SQL 执行层

如果这样能够成功:

2,260,121 rows
+
两个 CLOB

那么就进一步证明:

数据本身并非无法读取,主要问题集中在 Data Pump 的数据装载路径。

这也是本次案例最有价值的最终验证实验。


二十、不要直接一把梭 220 万行
#

第一次一定建议:

INSERT ...
SELECT ...
WHERE ORA_HASH(C_IDEN, 19) = 0
AND ROWNUM <= 1000;

COMMIT;

先验证:

1000 行
+
CLOB
+
DB Link
+
目标表

全部正常。

然后:

10000

再:

100000

最后才进行完整批次。

因为:

7 MB CLOB
+
几十万行

一次事务的:

UNDO
REDO
TEMP
网络传输
LOB 写入

都不能小看。


二十一、这次事故最后的结论
#

这次问题最终没有表现为传统意义上的:

ORA-01653
表空间不足

也不是:

ORA-39083
对象创建失败

而是:

Data Pump Worker
        ↓
TABLE_DATA
        ↓
T_S_BIZ_LOG
        ↓
klxprParseRow()
        ↓
SIGSEGV
        ↓
ORA-07445

同时:

PARALLEL=16

降低到:

PARALLEL=1

问题仍然存在。

而:

EXCLUDE T_S_BIZ_LOG

之后整个 Schema 可以正常恢复。

因此目前最合理的判断是:

Oracle 11.2.0.4 的 Data Pump 在处理这张包含两个大型 BASICFILE CLOB 的表时,触发了 klxprParseRow() 内部异常。公开资料中还可以找到 Oracle 后续版本针对 KLXPRPARSEROW / ORA-7445 的 Bug 31670014,因此高度怀疑属于 Oracle 内部 Bug 或特定数据模式触发的内部缺陷,但不能仅凭公开资料确认本次事故就是 Bug 31670014。


二十二、以后遇到类似问题,建议按照这个顺序排查
#

以后碰到:

impdp

突然:

fatal error

不要第一时间反复修改:

PARALLEL
TABLE_EXISTS_ACTION
REMAP_TABLESPACE

建议直接:

1. 找 alert
#

grep -i "ORA-07445" alert*.log

2. 找 trace
#

find $ORACLE_BASE/diag -name "*.trc" -mmin -60

3. 找函数
#

重点看:

ORA-07445:
[xxx()+offset]

例如本案例:

[klxprParseRow()+4516]

4. 看 Current SQL
#

确认:

SQL_ID
TABLE_NAME
MODULE
ACTION

5. 降到 PARALLEL=1
#

不是为了“解决”,而是为了验证:

是不是并行导致

6. 单独导入嫌疑表
#

例如:

impdp ... tables=gzdb.T_S_BIZ_LOG parallel=1

7. 排除嫌疑表重新恢复 Schema
#

如果:

排除以后成功

基本就可以锁定对象。

8. 最后考虑绕过 Data Pump
#

例如:

DB Link
+
INSERT SELECT

或者:

SQL*Loader

甚至:

中间文件

二十三、这次最重要的经验
#

Oracle 数据库迁移里有一个非常容易被忽视的问题:

Data Pump 报错,不代表数据一定坏了。

尤其是:

ORA-00600
ORA-07445
SIGSEGV
core dump

这种级别的问题。

如果普通 SQL 能正常读取:

SELECT
DBMS_LOB.GETLENGTH
DBMS_LOB.SUBSTR

而 Data Pump 在特定表的数据装载阶段直接把 Worker 打崩,那么就应该开始怀疑:

Oracle 内部 Bug

而不是继续无限纠结:

是不是 dump 文件坏了?
是不是表空间不够?
是不是 parallel 太高?
是不是权限不够?

本次案例尤其典型:

220 万行
+
两个 CLOB
+
最大 7.6 MB
+
Oracle 11.2.0.4
+
Data Pump
+
klxprParseRow
+
SIGSEGV

最后通过:

排除 T_S_BIZ_LOG

让整个 Schema 恢复成功,再把这张表单独处理。

这可能比为了“一次性 Data Pump 完整恢复”反复折腾更实际。


参考资料
#

Oracle 官方对 ORA-07445 的说明认为,这类错误本质上是 Oracle 代码触发的操作系统级异常,常见根因包括坏数据、配置问题以及 Oracle Bug。(Oracle Blogs)

Oracle 社区存在多个 Data Pump + ORA-07445 的历史案例,其中包括 Oracle 11.2.x 环境。(Oracle Forums)

Oracle 19.10 Bug Fix 列表明确记录:

31670014
AIM ORA-7445 [KLXPRPARSEROW] - KLXPRPARSEROW

与本次 klxprParseRow() 崩溃函数名高度一致。(DBA - Rodrigo Jorge)


最终处理路线
#

本案例最终采用:

gzdb
    │
    ├── Data Pump
    │      │
    │      ├── 其他对象 ─────────→ gzdb
    │      │
    │      └── T_S_BIZ_LOG ─────→ 排除
    │
    └── DB Link + INSERT SELECT
                 │
                 ↓
          T_S_BIZ_LOG

这种方案不漂亮。

但数据库恢复最重要的往往不是:

“一条命令全部恢复”

而是:

先把能恢复的恢复完
再把有问题的对象隔离
最后针对问题对象单独处理

对于老版本 Oracle,尤其是:

11.2.0.4

这种已经非常成熟但内部历史包袱也很多的版本,这种思路往往比死磕 Data Pump 更有效。

一个表把整个数据库恢复拖死,不如把这个表单独拎出来。

相关文章