☰
第九章 恢复系统
2026/10/10 17:28:05 网站建设 项目流程

第九章 恢复系统

9.1 故障分类

故障类型描述
事务故障逻辑错误:事务因内部错误条件无法完成;系统错误:DBMS 因错误条件(如死锁)必须终止活跃事务
系统崩溃电源故障或其他硬件/软件故障导致系统崩溃。Fail-stop 假设:非易失性存储内容不被系统崩溃破坏(数据库系统有大量完整性检查来防止磁盘数据损坏)
磁盘故障磁头碰撞或类似磁盘故障导致全部或部分磁盘存储损坏。损坏是可检测的(磁盘驱动器使用校验和检测故障)

9.2 恢复算法

恢复算法包含两个部分:

部分内容
正常运行期间确保有足够信息来从故障中恢复的操作
故障发生后将数据库恢复到保证原子性、一致性、持久性的状态的操作

核心问题:转账事务修改 A 和 B,若故障发生在一次修改后、另一次修改前:

  • 修改了数据库但事务未提交 → 数据库不一致
  • 事务已提交但未修改数据库 →更新丢失

9.3 存储结构

存储类型特点示例
易失性存储系统崩溃后不保留数据主存、高速缓存
非易失性存储系统崩溃后保留数据,但自身可能故障磁盘、磁带、闪存、NVRAM
稳定存储理论上的"永不故障"存储通过在不同非易失介质上维护多个副本近似实现

9.3.1 稳定存储的实现

策略:在独立磁盘上维护每个块的多个副本(可在远程站点保护火灾/洪水等灾难)。

数据传输中的故障防护(假设每个块有两个副本):

  1. 将信息写入第一个物理块
  2. 第一次写入成功后,将相同信息写入第二个物理块
  3. 仅当第二次写入成功完成后,输出操作才算完成

副本不一致的恢复:

  1. 查找不一致块 → 在非易失性存储上记录进行中的磁盘写入,恢复时仅比较这些块(而非比较全部块)
  2. 若某副本校验和错误 → 用另一个副本覆盖;若两者校验和均正确但内容不同 → 用第一个副本覆盖第二个

9.4 数据访问

9.4.1 基本操作

概念说明
物理块磁盘上的块
缓冲区块临时驻留在主存中的块

块移动操作:

操作含义
input(B)将物理块 B 传输到主存
output(B)将缓冲区块 B 写入磁盘,替换对应物理块

假设:每个数据项恰好存储在一个块中。

9.4.2 事务的私有工作区

每个事务TiT_iTi​有私有工作区,保存其访问和更新的所有数据项的本地副本。TiT_iTi​对数据项 X 的本地副本记为xix_ixi​。

操作含义
read(X)将数据项 X 的值赋给本地变量xix_ixi​
write(X)将本地变量xix_ixi​的值赋给缓冲区块中的数据项 X

⚠️output(B_X)不必紧跟在write(X)之后——系统可在适当时机执行。

访问规则:

  • 首次访问 X之前必须执行read(X)(后续读取可从本地副本)
  • write(X)可在事务提交前任意时间执行

9.5 恢复与原子性

为确保故障下的原子性,首先将描述修改的信息输出到稳定存储,而不修改数据库本身。

核心方法:

  • 日志恢复机制(重点)
  • 影子副本/影子分页(较少使用,详见教材)

9.6 基于日志的恢复

9.6.1 日志记录

日志是日志记录的序列,记录数据库上的更新活动信息。日志保存在稳定存储上。

时机日志记录
事务TiT_iTi​开始<Ti start>
TiT_iTi​执行write(X)之前<Ti, X, V1, V2>(V1 = 旧值,V2 = 新值)
TiT_iTi​完成最后一条语句<Ti commit>

9.6.2 两种日志方法

方法规则特点
立即修改未提交事务的更新可在事务提交前写入缓冲区或磁盘更新日志记录必须先于数据库项写入;磁盘输出可随时发生,顺序可与写入顺序不同
延迟修改仅在事务提交时才执行缓冲区/磁盘更新简化恢复的某些方面,但需存储本地副本的开销

9.6.3 事务提交

事务在<Ti commit>日志记录输出到稳定存储时被视为已提交。

  • 该事务所有先前的日志记录必须已经输出
  • 事务提交时其写入可能仍在缓冲区中,稍后再输出到磁盘

9.7 并发控制与恢复

在并发场景下:

  • 所有事务共享单个磁盘缓冲区和单个日志
  • 一个缓冲区块可能包含一个或多个事务更新的数据项
  • 日志记录可交错写入

关键假设(严格 2PL 保证):

若事务TiT_iTi​修改了某数据项,其他事务不能修改同一数据项,直到TiT_iTi​提交或中止。

——否则若T1T_1T1​更新 A →T2T_2T2​更新 A 并提交 →T1T_1T1​中止,如何进行 undo?


9.8 Undo 与 Redo 操作

9.8.1 Undo

undo(Ti):

  • 逆向扫描TiT_iTi​的日志记录,将所有被TiT_iTi​更新的数据项恢复为其旧值
  • 每次恢复时写出特殊日志记录<Ti, X, V>(V = 旧值)
  • Undo 完成后写出<Ti abort>

9.8.2 Redo

redo(Ti):

  • 正向扫描TiT_iTi​的日志记录,将所有被TiT_iTi​更新的数据项设置为其新值
  • 此过程不写日志

9.8.3 故障时的恢复决策

日志状态操作
有<Ti start>,无<Ti commit>或<Ti abort>UndoTiT_iTi​
有<Ti start>,且有<Ti commit>或<Ti abort>RedoTiT_iTi​

9.8.4 重复历史

若TiT_iTi​此前已 undo 并写出<Ti abort>记录,然后发生故障——恢复时会redoTiT_iTi​。

Redo 会重新执行TiT_iTi​的所有原始操作——包括恢复旧值的步骤。这称为重复历史。看似浪费,但大大简化恢复逻辑。

立即修改恢复示例:

时间点日志内容恢复动作
(a) 仅有 T0 的日志,无 commit<T0 start>,<T0, A, 1000, 950>,<T0, B, 2000, 2050>undo(T0):B→2000, A→1000,写<T0 abort>
(b) T0 已 commit,T1 未 commit含<T0 commit>+ T1 的 update 记录redo(T0) + undo(T1):A/B→950/2050,C→700,写<T1 abort>
© 两者均已 commit含<T0 commit>+<T1 commit>redo(T0) + redo(T1):A/B→950/2050,C→600

9.9 检查点

9.9.1 为什么需要检查点

  • 对整个日志中所有事务做 redo/undo极慢
  • 系统长时间运行后处理整个日志耗时巨大
  • 已将其更新输出到磁盘的事务会被不必要地 redo

通过周期性创建检查点来简化恢复过程。

9.9.2 检查点过程

  1. 将主存中所有日志记录输出到稳定存储
  2. 将所有已修改的缓冲区块输出到磁盘
  3. 将<checkpoint L>日志记录写入稳定存储(L= 检查点时所有活跃事务的列表)
  4. 创建检查点时停止所有更新

9.9.3 使用检查点的恢复

只需考虑在检查点之前开始的最近事务TiT_iTi​,以及TiT_iTi​之后开始的事务。

恢复流程:

  1. 从日志末尾逆向扫描,找到最近的<checkpoint L>记录
  2. 仅 L 中的事务 + 检查点后开始的事务需要 redo/undo
  3. 检查点前已提交/中止的事务——其更新已全部输出到稳定存储,可忽略
  4. 继续逆向扫描直到找到 L 中每个事务的<Ti start>记录
  5. 早于最早<Ti start>的日志部分——恢复不需要,可任意擦除

检查点恢复示例:

事务状态恢复动作
T1检查点前已提交可忽略(更新因检查点已输出到磁盘)
T2检查点后开始,有 commitRedo
T3检查点后开始,有 commitRedo
T4检查点后开始,无 commitUndo

本章重点:故障三分法(事务/系统/磁盘)与 fail-stop 假设、三层存储模型(易失/非易失/稳定存储)及稳定存储的多副本实现、日志作为恢复的基石——<Ti start>/<Ti, X, old, new>/<Ti commit>三类记录的含义与写入时机、立即修改 vs 延迟修改、undo(逆向恢复旧值)与 redo(正向设置新值)的算法、恢复判断准则(有 start 无 commit → undo,有 start 有 commit → redo)、重复历史策略、检查点的作用——大幅缩小恢复需扫描的日志范围 L。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询