首页 > 数据库 > Oracle > 记一次硬件故障后的数据库启动异常
记一次硬件故障后的数据库启动异常
大家好,我是睿。
最近处理了一次比较棘手的 Oracle 数据库故障。
数据库所在服务器发生硬件故障,主机宕机。硬件修复、服务器重新起来之后,数据库却无法正常启动。现场数据库版本是 Oracle 11.2.0.4 ,单机、归档模式,数据量大约 84GB ,而且没有可用备份。
这类故障最麻烦的地方,不是某一条命令怎么写,而是每一步都可能把后续恢复空间越压越小。
这次的处理链路大致如下:
硬件故障导致数据库异常中断
↓
数据文件头部 SCN 与控制文件记录不一致
↓
归档日志存在损坏,常规介质恢复无法继续
↓
在副本和原始数据保护的前提下,做控制性修复
↓
通过 BBED 对关键数据文件头部的检查点信息进行对齐
↓
处理 ORA-00600 [2662] ,推进数据库当前 SCN
↓
数据库打开后,屏蔽异常回滚段影响
↓
使用 Data Pump 导出,再导入到新的干净数据库
本文中的数据库名、主机名、目录、文件名和业务 schema 均已替换成模拟信息;进程号、归档序列号、 SCN 、错误码等排查证据保留,方便大家理解完整过程。
先强调一句:下面涉及隐藏参数、BBED 和 oradebug poke 的内容,属于极端故障下的应急抢救手段,不是日常运维建议。没有备份的数据库做这种操作,任何一个判断错误都可能造成不可逆的数据损失。实际环境应优先保留数据文件、控制文件、联机日志和归档日志的原始副本。
为了方便描述,本文统一使用下面这组模拟环境:
数据库名 : paycore
实例名 : paycore
主机名 : dbrecover01
Oracle 版本 : 11.2.0.4
部署方式 : 单机
归档模式 : ARCHIVELOG
数据库规模 : 约 84GB
备份情况 : 无可用备份
数据文件目录 : /u01/oradata/paycore/
归档目录 : /u01/arch/paycore/
故障环境启动到mount 报错,提示两个控制文件的版本不一致,并且报错信息中给出了两个控制文件的版本信息。
将数据库实例关闭
mv 掉版本较为旧的控制文件
从现有的控制文件中cp 一个同步文件
数据库可以启动到mount 状态
先查询控制文件中记录的数据文件检查点:
select file#,
checkpoint_change# || '' as checkpoint_change#
from v$datafile
order by file#;
结果如下:
FILE# CHECKPOINT_CHANGE#
----- -------------------
1 18234183707686
2 18234183707686
3 18234183707686
4 18234183707686
5 18234183707686
6 18234183707686
再查数据文件头部实际记录的检查点:
select file#,
checkpoint_change# || '' as checkpoint_change#
from v$datafile_header
order by file#;
结果如下:
FILE# CHECKPOINT_CHANGE#
----- -------------------
1 18234183611738
2 18234183611738
3 18234183707686
4 18234183707686
5 18234183707686
6 18234183707686
这里已经能看到明显的不一致:
控制文件记录的 SCN : 18234183707686
文件 1 、 2 头部记录的 SCN : 18234183611738
文件 3 ~ 6 头部记录的 SCN : 18234183707686
也就是说,文件 1 、 2 的文件头落后于控制文件和其他数据文件。
这里简单解释一下两个视图的区别:
V$DATAFILE
更接近控制文件中记录的数据文件检查点信息。
V$DATAFILE_HEADER
读取数据文件头部实际保存的检查点信息。
恢复时,Oracle 需要根据这些信息判断数据文件还需要应用哪些 redo 。如果两边的 SCN 对不上,就会进入介质恢复流程;如果所需 redo 又刚好损坏,恢复就会卡住。
继续查看 redo 日志的序列和 SCN 范围:
set linesize 600
select group#,
sequence#,
first_change# || '' as first_change#,
next_change# || '' as next_change#
from v$log
order by group#;
现场结果如下:
GROUP# SEQUENCE# FIRST_CHANGE# NEXT_CHANGE#
------ --------- ------------------ ------------------
1 37115 18234183707686 281474976710655
3 37114 18234183526121 18234183707686
2 37113 18234183359678 18234183526121
从这个结果可以看出:
1. 当前最新联机日志序列是 37115 。
2. 序列 37114 覆盖了从 18234183526121 到 18234183707686 之间的变化。
3. 文件 1 、 2 的头部 SCN 落后于最新检查点,需要依赖中间的 redo 完成恢复。
接下来尝试执行常规恢复:
recover database until cancel;
Oracle 提示需要线程 1 的某个变化记录:
ORA-00279: change 18234183611885 generated at 09/12/2026 10:51:32 needed for thread 1
ORA-00289: suggestion : /u01/arch/paycore/1_37114_1184811772.dbf
ORA-00280: change 18234183611885 for thread 1 is in sequence #37114
Specify log: {filename | AUTO | CANCEL}
输入 AUTO 后,恢复失败:
ORA-00283: recovery session canceled due to errors
ORA-00354: corrupt redo log block header
ORA-00353: log corruption near block 2288 change 18234183539243 time 09/12/2026 10:52:26
ORA-00334: archived log: '/u01/arch/paycore/1_37114_1184811772.dbf'
ORA-01112: media recovery not started
这组报错说明,常规恢复需要使用归档日志 37114 ,但该归档日志的块头已经损坏。
auto 方式失败后,笔者又常使用输入 redo 文件的方式,结果依然以失败告终, Oracle 无法继续读取后面的 redo 。
到这里,故障可以归纳为:
数据文件之间的 checkpoint SCN 不一致
+
恢复所需的归档日志/redo 存在损坏
=
常规 recover database 无法完成
看到归档损坏后,有些人会继续尝试更多次 recover database ,或者不断更换归档文件。
这次现场没有这样做,原因是日志范围已经比较清楚:
需要的变化号 : 18234183611885
归档序列 : 37114
损坏位置 : block 2288
损坏附近的 change# : 18234183539243
如果所需的关键 redo 本身已经损坏,而现场又没有可用备份,那么继续反复恢复并不能凭空产生缺失的 redo 。更重要的是,恢复过程中如果对原始文件做了不可逆修改,后续连最后的抢救机会也可能失去。
因此,在进行修改操作之前,做好故障环境的冷备,保留好现场,一旦发现自己步入歧途后,给自己一个回到原点机会。
先从 SPFILE 生成一个临时 PFILE :
create pfile='/home/oracle/init_paycore_recovery.ora' from spfile;
在临时 PFILE 中追加现场使用的隐藏参数:
*._allow_resetlogs_corruption=true
*._allow_error_simulation=true
这里必须再次强调:
隐藏参数不是普通恢复参数。
它们可能绕过 Oracle 的部分一致性检查。
它们只适用于明确知道自己在做什么的应急恢复场景。
这两个参数不应该直接写入长期使用的 SPFILE ,也不应该作为 “ 数据库启动报错 ” 的通用解决方案。本文保留它们,是为了还原这次无备份故障的实际处理过程。
使用临时 PFILE 启动到 mount :
startup mount pfile='/home/oracle/init_paycore_recovery.ora';
启动到 mount 后,才能在数据库不打开业务访问的情况下检查和修改数据文件头部。
这次修复的关键点,是把文件 1 、 2 的文件头检查点信息调整到与其他数据文件一致。
先看一个正常文件的数据文件头部。现场使用文件 5 作为参考:
BBED> set dba 5,1
DBA 0x01400001 (20971521 5,1)
BBED> dump offset 484 count 10
File: /u01/oradata/paycore/USERS02.DBF (5)
Block: 1
Offsets: 484 to 493
Dba:0x01400001
------------------------------------------------------------------------
26ec0a7a 95100000 cc67
再看文件 3 :
BBED> set dba 3,1
DBA 0x00c00001 (12582913 3,1)
BBED> dump offset 484 count 10
File: /u01/oradata/paycore/UNDOTBS01.DBF (3)
Block: 1
Offsets: 484 to 493
Dba:0x00c00001
------------------------------------------------------------------------
26ec0a7a 95100000 cc67
文件 3 和文件 5 的 offset 484 起始位置内容一致,这说明它们的文件头检查点信息一致。
继续打印 kcvfhckp 结构:
BBED> p kcvfhckp
struct kcvfhckp, 36 bytes @484
struct kcvcpscn, 8 bytes @484
ub4 kscnbas @484 0x7a0aec26
ub2 kscnwrp @488 0x1095
ub4 kcvcptim @492 0x4a2267cc
ub2 kcvcpthr @496 0x0001
这里与 SCN 相关的两个字段是:
kscnbas : 0x7a0aec26
kscnwrp : 0x1095
kscnbas 可以理解为 SCN 的 base 部分, kscnwrp 是 SCN 的 wrap 部分。换算时,可以用下面的公式:
完整 SCN = kscnwrp × 2^32 + kscnbas
先把十六进制转换为十进制:
0x7a0aec26
= 7 × 16^7 + 10 × 16^6 + 0 × 16^5 + 10 × 16^4
+ 14 × 16^3 + 12 × 16^2 + 2 × 16^1 + 6
= 2,047,536,166
0x1095
= 1 × 16^3 + 0 × 16^2 + 9 × 16 + 5
= 4,245
因此完整 SCN 为:
4,245 × 4,294,967,296 + 2,047,536,166
= 18,234,183,707,686
这与 V$DATAFILE 中文件 3 ~ 6 的检查点值一致:
18234183707686
这里还有一个容易看错的地方。
结构打印中显示的是:
kscnbas = 0x7a0aec26
但 dump 中看到的是:
26ec0a7a
这是因为 BBED dump 显示的是数据文件中的字节顺序,而多字节整数在这里按小端序保存。把字节反过来读:
26 ec 0a 7a
↓
0x7a0aec26
所以,不能直接把 dump 里的 26ec0a7a 当成正常书写顺序的十六进制整数。修改数据文件时,也必须按照文件中的字节顺序写入。
归档恢复时需要的变化号是:
18234183611885
它对应的十六进制完整值是:
0x10957a0975ed
拆开以后:
kscnwrp = 0x1095
kscnbas = 0x7a0975ed
对应的十进制换算为:
0x7a0975ed = 2,047,440,365
4,245 × 4,294,967,296 + 2,047,440,365
= 18,234,183,611,885
这也就是恢复提示中要求的 18234183611885 。
如果只是做十六进制到十进制的转换,也可以直接交给 Oracle 或操作系统:
select to_number('7A0AEC26', 'XXXXXXXX') as scn_base,
to_number('1095', 'XXXX') as scn_wrap
from dual;
printf '%d\n' 0x7a0aec26
printf '%d\n' 0x1095
不过,工具只能完成 “ 数值转换 ” ,不能替你判断字节序,也不能替你确认这个十六进制值是 kscnbas 、 kscnwrp ,还是一个完整 SCN 。结构和上下文仍然要结合 BBED 输出、数据文件头和日志范围来判断。
通过这组换算,可以把三类信息串起来:
BBED 文件头里的十六进制字段
↓
完整 SCN
↓
V$DATAFILE / V$DATAFILE_HEADER 中的十进制 checkpoint_change#
↓
recover database 提示的 change#
确认文件 3 、 5 的检查点值是目标值后,开始查看文件 1 和文件 2 。
文件 1 :
BBED> set dba 1,1
DBA 0x00400001 (4194305 1,1)
BBED> m /x 26ec0a7a offset 484
File: /u01/oradata/paycore/SYSTEM01.DBF (1)
Block: 1
Offsets: 484 to 493
Dba:0x00400001
------------------------------------------------------------------------
26ec0a7a 95100000 3461
文件 2 修改前:
BBED> set dba 2,1
DBA 0x00800001 (8388609 2,1)
BBED> dump offset 484 count 10
File: /u01/oradata/paycore/SYSAUX01.DBF (2)
Block: 1
Offsets: 484 to 493
Dba:0x00800001
------------------------------------------------------------------------
ed75097a 95100000 3461
文件 2 修改为与文件 3 、 5 相同的目标值:
BBED> m /x 26ec0a7a offset 484
File: /u01/oradata/paycore/SYSAUX01.DBF (2)
Block: 1
Offsets: 484 to 493
Dba:0x00800001
------------------------------------------------------------------------
26ec0a7a 95100000 3461
最后重新计算数据块校验值:
BBED> sum apply
Check value for File 1, Block 1:
current = 0x6bcf, required = 0x6bcf
BBED> sum apply
Check value for File 2, Block 1:
current = 0x488e, required = 0x488e
这一段操作可以概括为:
参考文件 3 、 5 的文件头
↓
确认目标 kscnbas = 0x7a0aec26
↓
按小端序写入 26ec0a7a
↓
重新计算 block 1 校验值
需要特别注意, offset 484 、文件头结构和目标值不是所有数据库都固定相同。它们与 Oracle 版本、数据块大小、文件类型和具体文件头有关。不能看到本文的 offset 后,就不检查自己的 kcvfhckp 结构,直接照抄。
BBED 是直接修改数据文件的工具,使用时必须做到:
1. 明确修改对象是哪个 file# 、哪个 block# 。
2. 修改前保存原始 dump 。
3. 修改后再次 dump ,确认写入的字节正确。
4. 执行 sum apply ,确认校验值更新。
5. 把每次修改记录下来,便于回退或复盘。
完成文件头 SCN 对齐后,尝试打开数据库。
第一次执行 OPEN RESETLOGS 时,出现:
ORA-00600: internal error code, arguments: [krsi_al_hdr_update.15], ...
现场将旧的 redo 文件移出工作目录后,再次尝试 OPEN RESETLOGS ,报错变为:
ORA-00600: internal error code, arguments: [2662], ...
这里需要区分两个问题:
[krsi_al_hdr_update.15]
与 redo / 日志头部更新过程有关。
[2662]
通常说明数据库当前 SCN 与数据文件或恢复过程中要求的 SCN
存在先后关系不一致。
在这种控制性恢复场景中, ORA-00600 [2662] 并不一定意味着又出现了一个全新的损坏对象。它可能是前面强行对齐数据文件头部后,数据库内存中的当前 SCN 还没有推进到足够大的值。
这也是为什么不能只看报错编号就下结论。要把它和前面的文件头 SCN 、控制文件 SCN 、恢复提示的 change# 放在同一条时间线上看。
现场通过 oradebug 查看并调整实例 SGA 中的当前 SCN :
oradebug setmypid
oradebug dumpvar sga kcsgscn_
oradebug poke 0x06001AE70 8 18234183827514
oradebug dumpvar sga kcsgscn_
本例中写入的目标值是十进制:
18234183827514
它对应的十六进制是:
0x10957a0cc03a
这个值高于前面数据文件的目标 SCN :
数据文件目标 SCN : 18234183707686
推进后的当前 SCN : 18234183827514
这样做的目的是让数据库当前 SCN 越过相关数据文件检查点和恢复边界,避免打开阶段再次因为 SCN 先后关系不满足而报 ORA-00600 [2662] 。
这里必须单独说明几个风险点:
1. 0x06001AE70 是本次实例启动后实际查到的内存地址,不是通用固定地址。
2. 不同版本、平台、启动方式和实例状态, SGA 地址可能变化。
3. oradebug poke 是直接写内存,写错地址或写错长度可能导致实例立即崩溃。
4. 推进 SCN 只是在恢复一致性检查层面跨过某些边界,不等于丢失的 redo 被找回来了。
5. 数据库能够 open ,不代表所有事务、索引和业务数据已经逻辑完整。
执行完成后,现场数据库正常打开:
alter database open;
数据库打开后,现场继续执行创建目录操作时,又遇到:
ORA-00600: internal error code, arguments: [4193], ...
这说明数据库虽然已经 open ,但 undo 层仍然存在不一致。 ORA-00600 [4193] 常见于事务表、回滚段或 undo 相关结构校验异常,在这种强制打开的数据库中并不意外。
这一步很容易被忽略:
实例能 open
≠
数据库已经健康
≠
所有 DDL 、 DML 、导出操作都可以正常执行
为了完成数据抢救,现场在临时 PFILE 中增加:
undo_management=MANUAL
然后使用该参数重新进入恢复工作状态。现场检查后确认,除了系统回滚段外,没有其他需要额外屏蔽的回滚段。
这里的目的不是把数据库改造成长期使用的手工回滚模式,而是尽量绕开异常 undo 结构对数据导出工作的影响。数据抢救完成后,应尽快迁移到新的干净数据库,不建议继续把这个实例当作正常生产库长期运行。
数据库打开后,第一目标不是马上恢复业务,而是尽快把可读数据导出。
先创建 Data Pump 目录:
create or replace directory expdp_dir
as '/u01/backup/paycore';
然后执行全库导出:
cd /u01/backup/paycore
nohup expdp '"/ as sysdba"' \
DIRECTORY=EXPDP_DIR \
DUMPFILE=paycore_full_%U.dmp \
FILESIZE=10G \
LOGFILE=paycore_full.log \
FULL=Y \
EXCLUDE=STATISTICS \
PARALLEL=1 \
CLUSTER=NO \
METRICS=Y \
> paycore_expdp.out 2>&1 &
echo $!
这几个参数分别有下面的考虑:
FULL=Y
按全库范围导出,便于后续重建环境。
EXCLUDE=STATISTICS
统计信息可以在新库中重新收集,减少导出内容和导入噪音。
PARALLEL=1
故障抢救阶段优先考虑稳定性,避免并行进一步增加资源压力。
CLUSTER=NO
本次是单机环境,显式关闭集群模式,减少环境差异。
METRICS=Y
便于在日志中查看对象级处理进度。
现场导出正常完成,没有出现新的报错。
这里建议不要只看命令返回码,还要检查 Data Pump 日志:
grep -E 'ORA-|UDE-|EXP-' paycore_full.log
同时确认 dump 文件数量、大小和生成时间,避免只生成了一个很小的空文件,却误以为导出成功。
数据导出后,准备导入到新的干净数据库。导入前先检查目标库字符集:
select rpad('Database character set', 30) as name,
value$ as value
from sys.props$
where name = 'NLS_CHARACTERSET';
在新库中创建导入目录:
create or replace directory impdp_dir
as '/u01/backup/paycore';
提前创建对应表空间,再执行导入。下面是导入示例:
cd /u01/backup/paycore
nohup impdp '"/ as sysdba"' \
DIRECTORY=IMPDP_DIR \
DUMPFILE=paycore_full_01.dmp \
LOGFILE=app_data_imp.log \
SCHEMAS=APP_DATA \
TABLE_EXISTS_ACTION=APPEND \
PARALLEL=1 \
CLUSTER=NO \
METRICS=Y \
> app_data_imp.out 2>&1 &
现场导入操作无报错。
不过,TABLE_EXISTS_ACTION=APPEND 需要结合目标库实际状态理解。它表示目标表已存在时追加数据。如果目标库中已经有部分数据,重复执行可能造成重复记录,因此要特别谨慎。
Data Pump 导出、导入日志无报错,只能说明工具层面完成了,不代表业务层面已经完全恢复。
至少要做下面几类验证。
对比源库导出前和目标库导入后的用户、表、索引、视图、序列、存储过程数量。
select owner,
object_type,
count(*) as object_count
from dba_objects
where owner = 'APP_DATA'
group by owner, object_type
order by object_type;
不要只抽查一张表。应根据业务重要性选择核心表,对比源库导出前的统计结果和目标库导入后的结果。
select count(*) as row_count
from APP_DATA.ORDER_HEADER;
select owner,
object_type,
object_name,
status
from dba_objects
where status <> 'VALID'
order by owner, object_type, object_name;
导入后部分视图、存储过程、触发器和包可能需要重新编译:
@?/rdbms/admin/utlrp.sql
执行前要评估数据库版本、业务窗口和编译影响。
grep -E 'ORA-|IMP-|UDI-' app_data_imp.log
除了看有没有 ORA 错误,还要关注:
1. 是否有对象被跳过。
2. 是否有表空间不存在。
3. 是否有权限不足。
4. 是否有约束、索引或 LOB 对象创建失败。
5. 是否有字符集转换提示。
最后还要让业务方参与验证:
1. 能否正常登录。
2. 核心查询结果是否符合预期。
3. 关键新增、修改、删除流程是否正常。
4. 报表、接口、批处理是否能正常运行。
5. 历史时间范围内的数据是否完整。
把现场信息串起来,故障过程可以还原成下面这样:
服务器硬件故障
↓
数据库异常中断
↓
文件 1 、 2 的文件头 checkpoint SCN 落后
↓
控制文件要求继续恢复到 change# 18234183611885
↓
需要的归档序列 37114 在 block 2288 附近出现损坏
↓
常规 recover database 无法继续
↓
使用文件 3 、 5 的文件头 SCN 作为参考
↓
把文件 1 、 2 的 kscnbas 调整到 0x7a0aec26
↓
按小端序写入 26ec0a7a ,并重新计算校验值
↓
打开阶段出现 ORA-00600 [2662]
↓
推进 SGA 当前 SCN 到 18234183827514
↓
数据库能够打开
↓
创建目录时出现 ORA-00600 [4193]
↓
临时使用 undo_management=MANUAL
↓
Data Pump 全库导出成功
↓
导入新的干净数据库
这次能完成数据迁移,依赖的是几个条件同时满足:
1. 仍然有可读取的数据文件。
2. 大部分数据文件头部 SCN 是一致的。
3. 损坏归档之外,其他数据库结构仍然可以被 Oracle 识别。
4. 能够使用参考数据文件推断目标检查点。
5. 打开后仍能启动 Data Pump 并读取业务对象。
如果数据文件本身存在大量物理坏块,或者数据字典、控制文件、undo 、 redo 同时严重损坏,处理结果可能完全不同。
这次是因为没有备份,才不得不走控制性恢复和逻辑迁移路线。BBED 、隐藏参数和 oradebug poke 能解决一些特定场景,但它们不能替代 RMAN 备份、归档备份和恢复演练。
对于没有备份的生产库,这次能把数据迁移到新环境,已经是比较幸运的结果。真正应该做的,是把这次故障当成一次提醒:备份、归档保护和恢复演练,永远比事后研究 BBED 更重要。
好了,今天的分享就到这里。如果你觉得这篇文章有用,
点赞 + 转发 + 关注,下期我们继续分享!
—— 睿 | Oracle 性能优化老司机
专注硬核干货,欢迎一起卷技术!

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70047128/viewspace-3182288/ ,如需转载,请注明出处,否则将追究法律责任。