数据库基础体系 · 第 107/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

Oracle Redo、归档与 Checkpoint:提交、恢复和写入路径

在 Oracle 中,“事务已经提交”“数据块已经写入数据文件”“日志已经归档”“数据库已经能够从故障中恢复”是四个不同状态。它们经常同时出现,却不能互相替代。

理解这几个状态,需要把一条修改从内存中的数据块开始,沿着以下路径追踪:

SQL 执行
  ├─ 修改 Buffer Cache 中的数据块
  ├─ 修改 Undo 段中的撤销信息
  └─ 生成 Redo,写入 Log Buffer
             │
             ├─ 提交时由 LGWR 写入 Online Redo Log
             ├─ 日志切换后由 ARCn/归档进程复制为 Archived Redo Log
             └─ Checkpoint 触发 DBWn 将脏数据块写入数据文件

恢复时方向相反:

数据文件或实例故障
  ├─ 从 Checkpoint 之后的 Redo 重做已发生的修改
  ├─ 根据事务提交记录判断哪些事务已经提交
  └─ 对未提交事务使用 Undo 回滚

下面分别解释这些组件、状态和故障路径。


一、先建立几个边界:Redo、Undo、数据文件不是同一种东西

1. Redo 是“如何重做修改”的日志

Redo 记录的是数据库为了恢复而需要的信息。它通常描述了某个数据块、Undo 块或其他数据库结构发生了什么变化,使恢复进程能够把修改重新应用到相应位置。

Redo 不是 SQL 文本日志。例如,下面的语句:

UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;

Redo 中并不要求保存完整的 SQL 字符串。恢复需要的是足以重建块修改的内部记录。

Redo 可能包含:

  • 表数据块的修改;
  • 索引块的修改;
  • Undo 段和 Undo 块的修改;
  • 数据字典或其他内部结构的修改;
  • 事务提交等事务状态信息。

因此,Undo 本身也需要 Redo 保护。否则,数据库可以重做表数据块,却无法可靠地重建事务的撤销信息。

2. Undo 是“如何撤销修改”的信息

Undo 主要服务于两个目标:

  1. 事务回滚;
  2. 读一致性。

假设事务把某行从 100 改成 50,Oracle 可以在当前块中看到新值,同时把旧值 100 保存在 Undo 中。一个较早 SCN 的查询可以通过 Undo 构造出它应该看到的旧版本。

Redo 和 Undo 的方向不同:

机制 主要问题 典型用途
Redo 如何把修改重新做出来 实例恢复、介质恢复、Data Guard
Undo 如何撤销修改或构造旧版本 回滚、读一致性

一个常见误解是:“有 Undo,所以重启后可以恢复事务。”不准确。实例恢复首先依赖 Redo 将数据文件推进到故障前状态,然后再根据事务状态和 Undo 清理未提交事务。Undo 不是 Redo 的替代品。

3. 数据块写入数据文件不等于事务提交

Oracle 使用 Buffer Cache。服务器进程通常先修改内存中的数据块,形成脏块;DBWn 在之后把脏块写入数据文件。

因此可能出现以下状态:

数据块已经写入数据文件,但事务尚未提交

这并不违反事务原子性。恢复时,如果发现该事务没有提交记录,就会使用 Undo 将它撤销。

也可能出现相反状态:

事务已经提交,但相应数据块还没有写入数据文件

这同样是正常的。只要提交对应的 Redo 已经持久化,恢复时就可以从 Redo 重做数据块修改。

这正是 Oracle 写前日志(Write-Ahead Logging,WAL)原则的核心:

在允许脏数据块写入数据文件之前,描述这些修改的 Redo 必须先写入持久化的 Redo Log。


二、Oracle 的三类主要持久化位置

1. Log Buffer

Log Buffer 位于 SGA 中,用于暂存各服务器进程生成的 Redo。

执行 DML 时,服务器进程通常完成以下动作:

  1. 在 Buffer Cache 中找到或读入数据块;
  2. 在需要时生成 Undo;
  3. 修改数据块和 Undo 块;
  4. 生成相应 Redo;
  5. 把 Redo 放入 Log Buffer。

Log Buffer 是内存,实例崩溃时其中尚未写出的内容会丢失。因此,事务提交不能仅依赖 Log Buffer。

2. Online Redo Log

Online Redo Log 是持久化的循环日志,由多个 redo log group 组成;每个 group 通常又包含一个或多个 member,以提供日志文件级别的镜像保护。

逻辑上,Oracle 在日志组之间循环使用:

GROUP 1 → GROUP 2 → GROUP 3 → GROUP 1 → ...

当前正在写入的组称为 CURRENT。已经写满并切换出去的组可能成为:

  • ACTIVE:其中的 Redo 仍可能被实例恢复需要;
  • INACTIVE:满足条件后可以被重新使用。

这里的状态不是“是否已经提交”的事务状态,而是日志组相对于数据库恢复和写入位置的状态。

3. Archived Redo Log

ARCHIVELOG 模式下,Oracle 在 Online Redo Log 发生日志切换后,把已填充的日志组复制为归档日志。归档日志通常由 ARCn 进程或相关归档机制完成。

归档日志是 Online Redo Log 的持久历史副本,不是另一种事务日志格式。它的主要价值是:

  • 保留已经被 Online Redo Log 覆盖的历史 Redo;
  • 支持从备份进行介质恢复;
  • 支持 RMAN 恢复;
  • 支持 Data Guard 的日志传输和应用。

归档发生在日志切换路径上,而不是每次 COMMIT 时单独生成一个归档文件。


三、一次 DML 到提交:完整写入路径

考虑以下事务:

UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;

UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;

COMMIT;

设事务开始前,数据库中的相关状态如下:

账户 1:1000
账户 2:500

第一步:读取并修改 Buffer Cache 中的数据块

如果包含 account_id = 1 的数据块不在 Buffer Cache,服务器进程先从数据文件读入;之后在内存中修改它:

数据文件:账户 1 = 1000
Buffer Cache:账户 1 = 900,状态为脏块

对于账户 2 也可能发生同样的过程。

此时,数据文件可能仍然是旧值。事务也还没有提交。

第二步:生成 Undo

为了支持回滚和读一致性,Oracle 需要保存旧版本信息:

Undo:
  账户 1 的旧值为 1000
  账户 2 的旧值为 500

Undo 块也可能位于 Buffer Cache 中,并且变成脏块。

第三步:生成 Redo

Oracle 为数据块和 Undo 块的变化生成 Redo:

Redo:
  修改账户 1 所在数据块
  修改账户 2 所在数据块
  修改相关 Undo 块

此时这些 Redo 可能只在 Log Buffer 中。

第四步:执行 COMMIT

默认情况下,提交需要等待 LGWR 将该事务的提交相关 Redo 写入 Online Redo Log。提交记录以及该事务相关的 Redo 持久化后,Oracle 才向客户端返回提交成功。

可以把提交的关键条件形式化为:

COMMIT 返回成功
    ⇒ 提交所需 Redo 已经交给操作系统并完成持久化写出

这里的“持久化”还依赖存储系统是否正确履行写入和缓存刷新语义。数据库可以要求操作系统和存储设备完成持久化,但如果硬件或虚拟化层错误地伪报写入完成,数据库软件无法弥补该故障。

提交时通常不要求 DBWn 同时把相关数据块写入数据文件:

提交成功:
  Redo 已持久化
  数据块可能仍在 Buffer Cache

第五步:稍后由 DBWn 写数据块

DBWn 在多种条件下写出脏块,例如:

  • Checkpoint 的写入要求;
  • Buffer Cache 空间压力;
  • 脏块过多;
  • 数据库后台写入调度;
  • 数据文件或表空间相关写入需求。

于是最终可能变成:

Online Redo Log:包含并已持久化事务 Redo
数据文件:包含账户 1 = 900、账户 2 = 600

如果此时数据库崩溃,恢复仍然要依赖 Redo 判断数据文件缺少哪些修改。


四、LGWR:为什么 COMMIT 通常等待日志写出

LGWR(Log Writer)负责把 Log Buffer 中的 Redo 写入 Online Redo Log。它不仅在提交时工作,还可能因为以下原因被唤醒:

  • 事务提交;
  • Log Buffer 达到一定使用程度;
  • DBWn 准备写入某个脏块,但对应 Redo 尚未写出;
  • 日志写入超时或后台调度;
  • 日志切换需要;
  • 其他内部恢复和日志管理需求。

1. 提交等待的是 Redo,不是数据块

错误的因果关系是:

COMMIT
  → DBWn 写数据文件
  → 数据文件安全

更准确的关系是:

COMMIT
  → LGWR 写提交相关 Redo
  → 提交成功返回
  → DBWn 以后再写数据块

这使得提交路径不必同步等待随机数据块写入。Redo 通常是顺序追加写,适合集中写出。

2. Group Commit

多个会话可能在相近时间提交。LGWR 可以一次写出多个事务的 Redo,这称为组提交(group commit)。

例如:

会话 A:提交,等待 LGWR
会话 B:提交,等待 LGWR
会话 C:提交,等待 LGWR

LGWR 一次写出包含 A、B、C 提交记录的日志范围后,三个会话都可能完成等待。

组提交并不改变事务语义:

  • 每个事务仍有自己的提交顺序和提交记录;
  • 某个事务的提交成功仍要求它所需的 Redo 已持久化;
  • 组提交只是把多个写请求合并。

3. COMMIT WRITE 的边界

Oracle 支持通过 COMMIT WRITE 指定提交写入行为,例如:

COMMIT WRITE WAIT IMMEDIATE;

常见语义是:

  • WAIT:等待 LGWR 完成必要的日志写出后再返回;
  • NOWAIT:不等待日志写出就返回;
  • IMMEDIATEBATCH:影响日志写请求的调度方式。

默认行为和具体可用选项应以目标 Oracle 版本和初始化参数为准。NOWAIT 不是“更快但完全等价”的提交。它允许客户端在 Redo 尚未持久化时就收到提交返回,因此发生实例故障时,刚刚返回成功的事务可能丢失。

这类提交通常不应作为一般业务事务的默认选择。它不是把数据库变成非事务系统,而是改变了客户端获得“提交成功”通知与日志持久化之间的等待边界。


五、Checkpoint:推进恢复起点,而不是“保存所有数据”

1. Checkpoint 的定义

Checkpoint 是数据库建立的一个恢复进度点。它通常包含一个 SCN 边界,表示:

在该边界之前,相关数据块已经按照恢复要求写入数据文件,恢复可以从更接近该位置的 Redo 开始。

Checkpoint 的直接目标是减少实例恢复所需扫描的 Redo 范围,并协调数据文件、控制文件和日志位置之间的恢复信息。

Checkpoint 不是:

  • 提交所有未提交事务;
  • 把所有脏块瞬间写完;
  • 删除 Checkpoint 之前的所有 Redo;
  • 替代归档;
  • 生成一个可独立恢复的备份。

2. Checkpoint 与 DBWn、CKPT 的关系

可以区分两个角色:

  • CKPT:协调 Checkpoint,更新控制文件和数据文件头中的相关 Checkpoint 信息,并通知需要处理的组件;
  • DBWn:将满足条件的脏数据块写入数据文件。

因此,Checkpoint 的数据流可以抽象为:

Checkpoint 请求
  → 确定目标 SCN 或相关范围
  → 通知 DBWn
  → DBWn 写出满足条件的脏块
  → 更新控制文件/数据文件头的恢复信息

现代 Oracle 的实际写入由多个后台进程、检查点队列和增量机制共同完成,不能简单理解为“CKPT 自己把所有数据写入文件”。

3. Checkpoint SCN 的直觉

假设某数据文件的 Checkpoint SCN 为 1000,表示恢复时至少需要考虑 1000 之后可能影响该文件的数据。

如果故障发生时数据库已经把相应数据块推进到 1500

数据文件 Checkpoint SCN:1000
故障时需要恢复到:1500

那么恢复至少需要处理大致位于:

SCN 1000 之后到故障时刻

的 Redo。

Checkpoint 越新,通常实例恢复需要处理的 Redo 越少;但 Checkpoint 越频繁或写入压力越强,也可能增加 DBWn 的写入和 I/O 竞争。这里没有“越频繁越好”的绝对结论。

4. Checkpoint 不改变事务提交状态

下面的事务没有提交:

UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;

ALTER SYSTEM CHECKPOINT;

Checkpoint 可能促使相关数据块写入数据文件,但它不会把事务变成已提交事务。故障恢复时:

数据块中的未提交修改可以已经落盘
事务没有提交记录
恢复阶段使用 Undo 将其撤销

反例是把“数据块已经落盘”当作“事务已经安全完成”。这会错误地把物理写入状态当成事务状态。


六、日志切换、归档和日志组复用

1. 日志切换是什么

当当前 Online Redo Log group 使用到需要切换的位置,Oracle 选择下一个可用的日志组,使其成为新的 CURRENT

典型状态变化类似:

切换前:
  Group 1:CURRENT
  Group 2:INACTIVE
  Group 3:INACTIVE

切换后:
  Group 1:ACTIVE 或等待归档
  Group 2:CURRENT
  Group 3:INACTIVE

具体状态还取决于实例恢复、归档和日志组配置。

日志切换不等于 Checkpoint:

  • 日志切换改变当前写入的日志组;
  • Checkpoint 推进数据文件的恢复进度;
  • 日志切换可能触发或伴随 Checkpoint 活动,但两者概念不同。

2. 为什么不能立即复用旧日志组

Oracle 不能在旧日志组仍可能被实例恢复需要时覆盖它。通常需要满足与以下因素相关的条件:

  1. 该日志组中的 Redo 已经不再是实例恢复所必需的;
  2. ARCHIVELOG 模式下,所需归档已经完成;
  3. 其他数据库状态和恢复约束允许复用。

如果归档目的地不可用,数据库可能无法安全复用日志组,最终表现为日志切换停滞,甚至出现无法分配新的日志组空间。

这是归档目录问题会反过来影响在线事务的原因:归档虽然是后台工作,但 Online Redo Log 是有限的循环空间。

3. 强制日志切换示例

在具备相应权限并确认环境允许时,可以观察日志切换:

SELECT log_mode, force_logging
FROM v$database;

SELECT group#, thread#, sequence#, status, archived
FROM v$log
ORDER BY thread#, group#;

ALTER SYSTEM SWITCH LOGFILE;

然后再次查询:

SELECT group#, thread#, sequence#, status, archived
FROM v$log
ORDER BY thread#, group#;

预期现象通常是:

  • CURRENT 的 group 发生变化;
  • sequence number 递增;
  • 原来的当前组变成其他状态;
  • ARCHIVELOG 模式下,归档列和归档视图会随后反映进度。

“随后”很重要:日志切换完成与归档进程完成复制不是同一个瞬间。

可以查看数据库的归档模式:

SELECT log_mode
FROM v$database;

查看归档日志记录:

SELECT thread#, sequence#, first_change#, next_change#,
       completion_time, deleted, status
FROM v$archived_log
ORDER BY thread#, sequence#;

这些视图通常需要查询目录对象的权限。生产环境中不要为了测试随意删除归档日志;归档日志是否可删除应由 RMAN 备份和恢复策略决定。


七、ARCHIVELOG 与 NOARCHIVELOG:恢复能力的根本差异

1. NOARCHIVELOG

NOARCHIVELOG 模式下,数据库通常只能依赖当前保留的 Online Redo Log 完成有限的实例恢复。数据库关闭后制作的备份可以用于某些恢复场景,但不能像归档模式那样将备份恢复到任意后续时间点。

其核心限制是:

旧的 Online Redo Log 被循环覆盖后,
数据库没有连续的历史 Redo 可用于介质恢复。

2. ARCHIVELOG

ARCHIVELOG 模式下,Online Redo Log 被切换出去后,可以保存为归档日志。于是可以形成:

数据文件备份
  + 一系列 Archived Redo Log
  + 必要时的 Online Redo Log
  → 将数据库恢复到备份之后的某个时间或 SCN

这也是 RMAN 时间点恢复、Data Guard 日志传输和许多生产恢复方案的基础。

但开启 ARCHIVELOG 并不自动意味着“备份可靠”:

  • 归档目的地可能不可用;
  • 归档文件可能损坏或丢失;
  • 没有可用的基线备份时,连续 Redo 也无法独立重建整个数据库;
  • 控制文件、参数文件、加密钱包等恢复所需内容也要纳入设计。

八、实例恢复:数据库崩溃后发生什么

实例恢复针对的是实例异常终止,例如主机断电或 Oracle 实例崩溃,但数据文件仍然可访问。

1. 为什么需要实例恢复

由于 DBWn 可以延迟写数据文件,故障发生时可能存在:

数据文件:
  只包含部分已提交修改
  也可能包含部分未提交修改

Online Redo:
  包含故障前已经写出的 Redo

数据文件因此可能处于“模糊”状态,不能仅靠查看文件中的值判断事务结果。

2. 实例恢复的两个主要阶段

可以将过程抽象成:

阶段一:前滚(roll forward)

从适当的 Checkpoint 位置开始,读取 Online Redo,把故障前已经发生但尚未落入数据文件的修改重做。

Checkpoint SCN = 1000
故障时可用 Redo 到 SCN = 1600

恢复读取:
  SCN 1000 → 1600

这里的目标不是“执行 SQL”,而是应用内部 Redo 记录,使数据块达到故障前应有的状态。

阶段二:回滚未完成事务

前滚可能把某些未提交事务的修改也重做了,因为这些修改在故障前确实已经发生。随后 Oracle 根据事务提交状态,使用 Undo 撤销没有提交记录的事务。

假设:

事务 T1:
  修改已写入 Redo
  提交记录也已写入 Redo

事务 T2:
  修改已写入 Redo
  但没有提交记录

恢复后:

T1 的修改保留
T2 的修改撤销

这解释了为什么恢复不能只做“把所有 Redo 重放一遍”。还必须判断事务是否完成。

3. 提交记录的作用

事务提交可以看作一个恢复判定点:

修改 Redo + 提交 Redo
  → 恢复后保留

只有修改 Redo,没有提交 Redo
  → 恢复后回滚

这不是说 Redo 中只有一条“提交日志”,而是说事务状态信息和修改记录共同决定恢复结果。


九、介质恢复:数据文件损坏时为什么需要归档日志

实例恢复假设数据文件仍在,只是其中的数据可能落后或包含未清理修改。

介质恢复针对更严重的情况,例如:

  • 数据文件丢失;
  • 数据文件损坏;
  • 磁盘或存储故障;
  • 需要把数据库恢复到某个时间点。

典型流程是:

1. 使用备份恢复数据文件
2. 数据文件回到备份时的较早状态
3. 应用备份之后的 Archived Redo Log
4. 必要时继续应用 Online Redo Log
5. 根据恢复目标执行介质恢复或时间点恢复

形式化地说,设数据文件备份对应的数据库状态为 SCN = 1000,目标是恢复到 SCN = 1500

Backup(1000)
  + Redo(1000, 1500]
  → Database(1500)

如果中间缺少任意必要日志,例如只有:

Redo(1000, 1200]
Redo(1300, 1500]

12001300 的日志没有备份或归档,那么通常不能无条件地完成连续恢复。恢复会在缺日志的位置停止。

这也是 RMAN 目录、归档备份和日志保留策略必须围绕“可恢复链”设计的原因。


十、备份与 Checkpoint:一致性备份不是“所有文件同一时刻复制”

1. 冷备份

数据库一致关闭后,数据文件、控制文件等处于一致状态,可以进行离线备份。此时仍需根据恢复目标保存相应日志和元数据。

2. 在线备份

在线备份时,数据文件可能在复制过程中继续变化。一个文件甚至可能在复制期间包含不同时间点的块,因此它不是简单的“某一时刻的静态快照”。

Oracle 通过 Redo 使这种备份具备恢复基础:

在线备份得到一个可能不完全一致的文件副本
  + 备份期间及之后所需的 Redo
  → 恢复到一致状态

因此,不能因为某个数据文件复制完成,就认为只要这个文件本身便可恢复。在线备份必须配套保存所需的 Redo。

3. Checkpoint 不等于备份一致性

Checkpoint 可以减少数据库实例恢复的工作量,但它不会:

  • 复制数据文件;
  • 复制控制文件;
  • 保存归档日志;
  • 验证备份是否可恢复;
  • 为在线备份自动建立完整的恢复链。

RMAN 会结合数据库的 SCN、文件状态和 Redo 需求管理备份与恢复;手工复制文件时尤其不能把 Checkpoint 当成备份工具。


十一、数据流中的并发与顺序约束

1. 多会话同时修改

多个会话可以并发生成 Redo,并写入共享的 Log Buffer。Oracle 必须为 Redo 分配顺序位置,并处理事务提交顺序、块修改顺序和日志写出顺序。

一个事务的提交返回不能只看“它的部分 Redo 是否写出”,而要确保恢复所需的相关日志范围已经满足持久化要求。

2. DBWn 不能违反 WAL

假设某个脏数据块包含 SCN 为 2000 的修改,而对应 Redo 还只在 Log Buffer 中:

数据块:准备写入数据文件
对应 Redo:尚未持久化

DBWn 不能安全地先写这个数据块。否则断电后,数据文件已经包含修改,但 Redo 丢失,恢复无法保证一致性。

因此,数据库写入路径中有一个关键约束:

Data Block Write
    必须晚于
对应 Redo 的持久化

这就是 WAL 在 Oracle 中的实际工程含义。

3. 提交顺序与数据块写入顺序可以不同

假设:

事务 T1 先修改块 A,但暂未提交
事务 T2 后修改块 B,并提交
DBWn 先写块 A
LGWR 先写 T2 的提交 Redo

这不是异常。块写入顺序不需要等于事务提交顺序。恢复阶段会根据 Redo 和事务状态重新确定:

T1:没有提交,撤销
T2:有提交,保留

十二、RAC 中的额外边界:Redo 有 thread

在 Oracle RAC 中,每个实例通常有自己的 Redo thread。多个实例并发运行时:

Instance 1 → Thread 1 → Redo sequence 101, 102, ...
Instance 2 → Thread 2 → Redo sequence 205, 206, ...

因此,归档和恢复不能只看单一的 sequence#,还要结合 thread#

查询日志信息时应至少关注:

SELECT thread#, sequence#, first_change#, next_change#
FROM v$archived_log
ORDER BY thread#, sequence#;

恢复某个 RAC 数据库时,需要覆盖恢复目标所需的各个 thread。只保存其中一个实例的归档日志,可能无法完成整个数据库的连续恢复。

这也是 Data Guard、RMAN 和 RAC 恢复边界不能只用“日志序列号连续”一句话描述的原因:连续性必须在正确的 thread 范围内判断。


十三、Data Guard 中的“传输”和“应用”不是一回事

在 Data Guard 场景中,主库生成 Redo 后,通常还要经历:

主库生成 Redo
  → Redo transport 传输到备库
  → 备库接收并保存
  → Redo apply 应用到备库数据文件

因此存在多个不同位置:

  1. 主库 LGWR 已写出;
  2. 主库归档完成;
  3. 备库已经接收;
  4. 备库已经应用;
  5. 备库数据文件已经推进到相应恢复位置。

“备库已经收到日志”不等于“备库已经应用日志”。同步传输、异步传输、保护模式和具体配置会影响提交所等待的远程确认边界,但不改变 Redo、Checkpoint 和事务提交各自的基本概念。


十四、用动态性能视图观察实际状态

以下查询适合在测试库或具有相应权限的环境中执行。

1. 查看归档模式

SELECT name, log_mode, open_mode, database_role
FROM v$database;

关注:

  • LOG_MODEARCHIVELOGNOARCHIVELOG
  • OPEN_MODE:数据库当前打开状态;
  • DATABASE_ROLE:主库、物理备库等角色。

2. 查看 Online Redo Log 组

SELECT group#, thread#, sequence#, bytes,
       members, archived, status, first_change#
FROM v$log
ORDER BY thread#, group#;

重点理解:

  • CURRENT:当前正在使用的日志组;
  • ACTIVE:仍可能与恢复有关;
  • INACTIVE:通常可以在满足其他条件后复用;
  • ARCHIVED:反映归档状态,但不能单独替代完整恢复判断。

3. 查看归档目的地状态

SELECT dest_id, status, destination,
       error, archived_thread#, archived_seq#
FROM v$archive_dest
WHERE status <> 'INACTIVE'
ORDER BY dest_id;

如果出现归档错误,应先确认:

  • 目标路径是否存在;
  • 空间是否耗尽;
  • 权限是否正确;
  • 网络或远程目的地是否可用;
  • 是否有归档日志积压。

4. 查看 Checkpoint 相关信息

SELECT checkpoint_change#, current_scn
FROM v$database;

不同版本和视图提供的字段并不完全相同,数据文件层面还可以查询:

SELECT file#, checkpoint_change#, checkpoint_time,
       fuzzy, recover
FROM v$datafile_header
ORDER BY file#;

这些字段用于观察数据文件头中的恢复相关状态。不要仅凭一次查询就断言“整个数据库已经安全备份”;需要结合备份工具的元数据、控制文件和 Redo 链验证。


十五、常见失败表现及其因果关系

1. 日志切换频繁,归档跟不上

可能看到:

日志组无法及时复用
日志切换等待增加
归档目的地报错

因果链通常是:

日志生成速度上升
  → Online Redo Log 更快填满
  → 需要更频繁切换
  → ARCn 需要更快归档
  → 归档目的地、I/O 或网络成为瓶颈
  → 日志组不能及时复用

增加日志组数量可以暂时提供缓冲,但如果归档目的地永久不可用,最终仍会耗尽空间。它不是根治手段。

2. ORA-00257 等归档相关错误

典型原因是归档目的地空间不足或无法写入。此时数据库可能无法继续安全地循环使用日志组。

正确排查方向包括:

  1. 查看 V$ARCHIVE_DEST 错误;
  2. 检查文件系统、ASM 磁盘组或远程目标;
  3. 确认归档日志是否已经按恢复策略备份;
  4. 使用 RMAN 按策略删除可安全删除的日志;
  5. 修复归档目的地后验证新的日志切换和归档。

不能直接在操作系统中删除仍可能被恢复或 Data Guard 使用的归档文件。

3. ORA-01555:snapshot too old

这类错误通常与 Undo 保留不足、长查询和高并发修改有关,不是“Redo 不够”。

Redo 主要帮助恢复修改;Undo 主要帮助长查询构造一致读版本。如果长查询需要的旧版本已经被覆盖,就可能出现快照过旧。

这说明:

Redo 完整
  ≠ Undo 保留足够

4. 恢复时提示需要某个日志序列

恢复过程要求某个归档日志,通常意味着当前数据文件状态与目标恢复位置之间存在 Redo 缺口。此时应核对:

  • 日志的 thread#
  • sequence#
  • first_change#next_change#
  • 日志文件是否损坏;
  • 是否恢复了正确的备份;
  • 是否存在另一份归档副本。

不能只按文件名猜测日志顺序。


十六、几个必须避免的等价替换

错误一:COMMIT 就是“写数据文件”

正确理解:

COMMIT 的持久化核心是提交 Redo
数据块由 DBWn 异步或稍后写入数据文件

错误二:Checkpoint 就是提交

正确理解:

Checkpoint 推进数据文件的恢复进度
COMMIT 决定事务是否完成

一个未提交事务可以在 Checkpoint 后出现在数据文件中,恢复时仍会被撤销。

错误三:日志切换就是归档完成

正确理解:

日志切换:切换当前 Online Redo Log group
归档完成:历史日志已经复制到归档目的地

归档可能在切换后继续进行。

错误四:有一份数据文件备份就能恢复

正确理解:

备份提供某个基线状态
Redo 提供从基线到目标状态的变化
两者必须形成可验证的恢复链

错误五:归档日志就是备份

归档日志只保存 Redo,不包含完整数据库数据块。它不能单独替代数据文件备份。反过来,只有数据文件备份而没有所需 Redo,也可能无法恢复到目标时间点。


十七、把一次故障按时间线完整推导

假设数据库发生以下事件:

SCN 1000:完成某次 Checkpoint
SCN 1100:事务 T1 修改块 A,但未提交
SCN 1200:事务 T2 修改块 B,并提交
SCN 1300:DBWn 写出块 A
SCN 1400:数据库异常断电

同时假设:

  • T1 的修改 Redo 已写入 Online Redo Log;
  • T1 没有提交记录;
  • T2 的修改 Redo 和提交记录都已写入 Online Redo Log;
  • 块 B 尚未由 DBWn 写入数据文件。

故障时可能是:

数据文件:
  块 A:已经包含 T1 的未提交修改
  块 B:仍是旧状态

Redo:
  包含 T1 的修改
  包含 T2 的修改
  包含 T2 的提交记录

恢复步骤:

步骤一:从 SCN 1000 开始前滚

恢复读取 SCN 1000 之后的 Redo:

重做 T1 对块 A 的修改
重做 T2 对块 B 的修改

现在两个事务的修改都可能存在于恢复后的数据块中。

步骤二:检查事务状态

T1:没有提交记录
T2:有提交记录

步骤三:回滚 T1

使用 T1 的 Undo 撤销块 A 的修改。

步骤四:保留 T2

块 B 保留 T2 的已提交修改。

最终状态:

T1:回滚
T2:提交并保留

这个算例同时说明:

  • Checkpoint 决定恢复大致从哪里开始;
  • Redo 使数据块可以前滚;
  • 提交 Redo 决定事务是否保留;
  • Undo 负责撤销未提交事务;
  • DBWn 是否已经写出某个数据块,不决定事务最终是否提交。

十八、生产恢复能力应如何验证

恢复能力不是由某个单独参数保证的,而是由一整条可用链路保证:

可读取的备份
+ 正确的控制文件和元数据
+ 连续且未损坏的 Redo
+ 足够的归档保留
+ 可用的恢复主机和存储
+ 已验证的恢复步骤

在 Oracle 环境中,RMAN 负责记录和执行大量备份恢复工作。实际操作中应区分:

  • CROSSCHECK:检查备份或归档记录对应的文件是否仍可访问;
  • VALIDATE:验证备份或数据库文件是否可读、结构是否满足检查;
  • RESTORE ... PREVIEW:预览恢复所需的备份;
  • RESTORE:恢复文件;
  • RECOVER:应用 Redo。

这些动作的具体语法和可用选项会受 Oracle 版本、数据库角色、备份介质和权限影响,不能把“备份命令执行成功”直接等同于“已经验证可恢复”。真正可靠的验证应在隔离环境中执行一次完整恢复,至少覆盖目标数据库、所需时间点和关键业务对象。


结语:用四条线理解 Oracle 的恢复语义

Oracle 的写入和恢复路径可以归纳为四条同时推进、但互不等价的线:

  1. 事务线:事务是否提交,由提交记录和事务状态决定;
  2. Redo 线:修改是否能够在故障后重做,由 Redo 是否生成并持久化决定;
  3. 数据文件线:脏块何时落盘,由 DBWn 和 Checkpoint 等写入机制决定;
  4. 归档线:历史 Redo 能否在 Online Redo 被覆盖后继续使用,由归档和备份保留策略决定。

最重要的恢复逻辑是:

提交成功
  → 提交相关 Redo 已持久化

数据块已落盘
  → 不代表事务已提交

Checkpoint 已完成
  → 不代表已经备份

日志已切换
  → 不代表归档已经完成

归档存在
  → 不代表存在完整的数据文件恢复基线

将这些边界分开之后,LGWR、DBWn、CKPT、ARCn、Online Redo Log、Archived Redo Log、Undo 和 SCN 就不再是互相混淆的名词,而是一条可以从事务执行一直追踪到故障恢复的完整机制链。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。