ITPub博客

首页 > 数据库 > Oracle > 记一次硬件故障后的数据库启动异常

记一次硬件故障后的数据库启动异常

原创 Oracle 作者:marui 时间:2026-09-14 12:29:59 0 删除 编辑

记一次硬件故障后的数据库启动异常

大家好,我是睿。

最近处理了一次比较棘手的 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 状态

二、先确认数据文件 SCN 是否一致

先查询控制文件中记录的数据文件检查点:

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 ?

看到归档损坏后,有些人会继续尝试更多次 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 后,才能在数据库不打开业务访问的情况下检查和修改数据文件头部。

六、BBED 中的 SCN 到底是什么?

这次修复的关键点,是把文件 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 起始位置内容一致,这说明它们的文件头检查点信息一致。

6.1 从 BBED 字节看 SCN

继续打印 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

6.2 为什么 dump 中是 26ec0a7a ?

这里还有一个容易看错的地方。

结构打印中显示的是:

kscnbas = 0x7a0aec26

但 dump 中看到的是:

26ec0a7a

这是因为 BBED dump 显示的是数据文件中的字节顺序,而多字节整数在这里按小端序保存。把字节反过来读:

26 ec 0a 7a

↓

0x7a0aec26

所以,不能直接把 dump 里的 26ec0a7a 当成正常书写顺序的十六进制整数。修改数据文件时,也必须按照文件中的字节顺序写入。

6.3 现场恢复所需的 change# 也可以反推

归档恢复时需要的变化号是:

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#

七、对齐文件 1 、 2 的检查点信息

确认文件 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. 把每次修改记录下来,便于回退或复盘。

八、RESETLOGS 后的 ORA-00600 [2662]

完成文件头 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# 放在同一条时间线上看。

九、推进数据库当前 SCN

现场通过 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;

十、数据库能 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 导出、导入日志无报错,只能说明工具层面完成了,不代表业务层面已经完全恢复。

至少要做下面几类验证。

14.1 对比对象数量

对比源库导出前和目标库导入后的用户、表、索引、视图、序列、存储过程数量。

select owner,
       object_type,
       count(*) as object_count
from dba_objects
where owner = 'APP_DATA'
group by owner, object_type
order by object_type;

14.2 对比关键表行数

不要只抽查一张表。应根据业务重要性选择核心表,对比源库导出前的统计结果和目标库导入后的结果。

select count(*) as row_count
from APP_DATA.ORDER_HEADER;

14.3 检查无效对象

select owner,
       object_type,
       object_name,
       status
from dba_objects
where status <> 'VALID'
order by owner, object_type, object_name;

导入后部分视图、存储过程、触发器和包可能需要重新编译:

@?/rdbms/admin/utlrp.sql

执行前要评估数据库版本、业务窗口和编译影响。

14.4 检查 Data Pump 日志

grep -E 'ORA-|IMP-|UDI-' app_data_imp.log

除了看有没有 ORA 错误,还要关注:

1. 是否有对象被跳过。

2. 是否有表空间不存在。

3. 是否有权限不足。

4. 是否有约束、索引或 LOB 对象创建失败。

5. 是否有字符集转换提示。

14.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/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
要得到你想要的某样东西,最可靠的办法是让你自己配得上它。

注册时间:2025-07-21

  • 博文量
    30
  • 访问量
    31693