ARIES:Recovery Algorithm(论文笔记)
发布于:
论文:ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Partial Rollbacks(Mohan et al., ACM TODS 1992)
ARIES 是 IBM 开发的数据库崩溃恢复算法,是现代数据库恢复机制的基础。本文深入解析 ARIES 的核心思想、三阶段恢复流程和工程实践。
1. 崩溃恢复的挑战
1.1 恢复需要满足的 ACID 属性
数据库崩溃恢复必须同时满足 ACID 属性的要求:
graph TD
A[ACID属性] --> B[Durability<br/>持久性]
A --> C[Atomicity<br/>原子性]
A --> D[Consistency<br/>一致性]
A --> E[Isolation<br/>隔离性]
B --> B1[已提交事务的数据不丢失]
B1 --> B2[即使系统崩溃也能恢复]
C --> C1[事务要么全部提交要么全部回滚]
C1 --> C2[不能出现部分写入]
D --> D1[数据库约束始终满足]
D1 --> D2[恢复后数据一致]
E --> E1[并发事务互不干扰]
E1 --> E2[恢复不影响隔离性]
style B fill:#e3f2fd
style C fill:#fff3e0
style D fill:#e8f5e9
style E fill:#f3e5f5
恢复的核心挑战:
- 持久性(Durability)
- 已提交事务的数据必须持久化
- 即使系统崩溃,已提交的数据也不能丢失
- 需要将数据写入非易失性存储(磁盘)
- 原子性(Atomicity)
- 事务要么全部提交,要么全部回滚
- 不能出现部分写入的情况
- 崩溃时未提交的事务必须回滚
- 性能要求
- 恢复时间要尽可能短
- 日志量要尽可能少
- 实现复杂度要可控
1.2 传统恢复方法的问题
在 ARIES 之前,数据库恢复方法存在以下问题:
Steal/No-Force 策略的挑战:
- Steal:允许将未提交事务的脏页写回磁盘(提高性能)
- No-Force:提交时不必强制写回所有脏页(提高性能)
- 问题:崩溃后需要区分哪些是已提交的,哪些是未提交的
传统方法的局限性:
graph TD
A[传统恢复方法] --> B[Shadow Paging]
A --> C[Simple WAL]
B --> B1[需要复制整个页]
B1 --> B2[空间开销大]
B2 --> B3[不支持并发]
C --> C1[日志量大]
C1 --> C2[恢复时间长]
C2 --> C3[不支持部分回滚]
style B fill:#e3f2fd
style C fill:#fff3e0
ARIES 的创新:
- 页级 LSN:每个页记录最后应用的日志序号
- Redo/Undo 分离:精确控制重做和撤销的范围
- Fuzzy Checkpoint:支持增量检查点,减少恢复时间
- 部分回滚:支持事务的部分回滚,提高灵活性
2. ARIES 的核心机制
2.1 WAL:Write-Ahead Logging
WAL 是 ARIES 的基础,所有修改必须先写日志再写数据页:
sequenceDiagram
participant Txn as Transaction
participant Buffer as Buffer Pool
participant Log as WAL
participant Disk as Disk
Txn->>Buffer: 修改数据页
Buffer->>Log: 写入日志记录
Log->>Disk: 刷新日志到磁盘
Disk-->>Log: Success
Log-->>Buffer: Success
Buffer->>Buffer: 标记页为脏页
Note over Buffer,Disk: 数据页可以延迟写入磁盘
WAL 的优势:
- 持久性保证:日志先写磁盘,即使数据页丢失也能恢复
- 顺序写入:日志是顺序写入,性能高
- 原子性保证:通过日志可以精确恢复或回滚
WAL 的写入规则:
- 日志先行:修改数据页前必须先写日志
- 强制刷新:提交事务前必须将日志刷新到磁盘
- 顺序写入:日志必须按顺序写入,不能跳跃
2.2 LSN:Log Sequence Number
LSN 是日志序号,贯穿页与日志记录的关联:
classDiagram
class LogRecord {
+ LSN lsn
+ TransactionID transId
+ PageID pageId
+ LogType type
+ Data oldValue
+ Data newValue
+ LSN prevLSN
}
class Page {
+ PageID pageId
+ LSN pageLSN
+ Data data
}
class Transaction {
+ TransactionID transId
+ LSN lastLSN
+ Status status
}
LogRecord --> Page : pageLSN指向最后应用的日志
LogRecord --> Transaction : 属于某个事务
Transaction --> LogRecord : 维护lastLSN
LSN 的作用:
- 页级 LSN(PageLSN)
- 每个数据页记录最后应用的日志序号
- 用于判断页是否需要 Redo
- 如果
PageLSN < LogLSN,说明页需要 Redo
- 事务级 LSN(LastLSN)
- 每个事务记录最后写入的日志序号
- 用于构建事务的日志链
- 支持部分回滚和完整回滚
- 日志链
- 通过
prevLSN字段构建事务的日志链 - 支持从最新日志回溯到事务开始
- 用于 Undo 操作
- 通过
LSN 的分配规则:
- 单调递增:LSN 全局单调递增,保证顺序
- 唯一性:每个日志记录有唯一的 LSN
- 持久化:LSN 写入日志和页,保证可恢复
2.3 Redo/Undo 分离
ARIES 的核心创新是 Redo 和 Undo 的分离:
graph TD
A[日志记录] --> B[Redo信息]
A --> C[Undo信息]
B --> B1[如何重做操作]
B1 --> B2[newValue]
B2 --> B3[用于恢复已提交事务]
C --> C1[如何撤销操作]
C1 --> C2[oldValue]
C2 --> C3[用于回滚未提交事务]
D[恢复阶段] --> E[Redo阶段]
D --> F[Undo阶段]
E --> E1[重放所有日志]
E1 --> E2[恢复到崩溃前状态]
F --> F1[撤销未提交事务]
F1 --> F2[写CLR补偿日志]
style B fill:#e3f2fd
style C fill:#fff3e0
style E fill:#e8f5e9
style F fill:#f3e5f5
Redo 的特点:
- 幂等性:Redo 操作是幂等的,可以重复执行
- 条件执行:只有当
PageLSN < LogLSN时才执行 Redo - 恢复已提交:用于恢复已提交但未刷盘的修改
Undo 的特点:
- 补偿日志(CLR):Undo 时写入补偿日志,记录撤销操作
- 可重复撤销:如果 Undo 过程中再次崩溃,可以继续 Undo
- 撤销未提交:用于撤销未提交事务的修改
3. ARIES 三阶段恢复
3.1 Analysis 阶段:重建状态
Analysis 阶段的目标是重建崩溃时的系统状态:
graph TD
A[Analysis阶段] --> B[扫描日志]
B --> C[重建事务表]
B --> D[重建脏页表]
B --> E[确定Redo起点]
C --> C1[活跃事务列表]
C1 --> C2[每个事务的lastLSN]
D --> D1[脏页列表]
D1 --> D2[每个页的recLSN]
E --> E1[从最小recLSN开始Redo]
style A fill:#e3f2fd
style C fill:#fff3e0
style D fill:#e8f5e9
style E fill:#f3e5f5
Analysis 阶段的流程:
- 从 Checkpoint 开始扫描
- 从最近的 Checkpoint 开始扫描日志
- Checkpoint 记录了事务表和脏页表的快照
- 重建事务表(Transaction Table)
- 遇到
BEGIN日志:添加事务到事务表 - 遇到
COMMIT日志:标记事务为已提交 - 遇到
ABORT日志:标记事务为回滚中 - 更新每个事务的
lastLSN
- 遇到
- 重建脏页表(Dirty Page Table)
- 遇到
UPDATE日志:如果页不在脏页表中,添加并记录recLSN recLSN:页第一次变脏时的 LSN- 用于确定 Redo 的起点
- 遇到
- 确定 Redo 起点
- Redo 起点 = min(所有脏页的 recLSN, Checkpoint 的 LSN)
- 从这个点开始 Redo,保证所有脏页都能恢复
事务表和脏页表的结构:
classDiagram
class TransactionTable {
+ map_TransID_TransEntry entries
+ AddTransaction()
+ UpdateLastLSN()
+ MarkCommitted()
}
class TransEntry {
+ TransactionID transId
+ LSN lastLSN
+ Status status
}
class DirtyPageTable {
+ map_PageID_PageEntry entries
+ AddPage()
+ GetRecLSN()
}
class PageEntry {
+ PageID pageId
+ LSN recLSN
}
TransactionTable --> TransEntry : 包含
DirtyPageTable --> PageEntry : 包含
3.2 Redo 阶段:恢复到崩溃前状态
Redo 阶段的目标是将系统恢复到崩溃前的状态:
sequenceDiagram
participant Recovery as Recovery Manager
participant Log as Log File
participant Buffer as Buffer Pool
participant Disk as Disk
Recovery->>Log: 从Redo起点开始扫描
Log-->>Recovery: LogRecord
loop 遍历每个日志记录
Recovery->>Recovery: 检查日志类型
alt 是UPDATE日志
Recovery->>Buffer: 检查页的PageLSN
alt PageLSN < LogLSN
Recovery->>Buffer: 执行Redo操作
Buffer->>Buffer: 更新PageLSN
Buffer->>Disk: 写回页(可选)
else PageLSN >= LogLSN
Recovery->>Recovery: 跳过(已应用)
end
end
end
Recovery->>Recovery: Redo完成
Redo 阶段的规则:
- 条件 Redo
- 只有当
PageLSN < LogLSN时才执行 Redo - 如果
PageLSN >= LogLSN,说明页已经应用了该日志,跳过
- 只有当
- 幂等性
- Redo 操作是幂等的,可以重复执行
- 即使重复 Redo 也不会出错
- 不区分事务状态
- Redo 阶段不区分事务是否已提交
- 所有日志都重放,恢复到崩溃前的物理状态
Redo 的优化:
- 批量 Redo:可以批量读取日志,提高效率
- 并行 Redo:不同页的 Redo 可以并行执行
- 延迟写回:Redo 后的页可以延迟写回磁盘
3.3 Undo 阶段:撤销未提交事务
Undo 阶段的目标是撤销所有未提交事务的修改:
graph TD
A[Undo阶段] --> B[遍历未提交事务]
B --> C[从lastLSN开始]
C --> D[沿prevLSN回溯]
D --> E[执行Undo操作]
E --> F[写CLR补偿日志]
F --> G[更新页的PageLSN]
G --> H{还有日志?}
H -->|是| D
H -->|否| I[事务Undo完成]
style A fill:#e3f2fd
style E fill:#fff3e0
style F fill:#e8f5e9
Undo 阶段的流程:
- 遍历未提交事务
- 从 Analysis 阶段构建的事务表中获取未提交事务
- 按
lastLSN从大到小排序(最新的先撤销)
- 沿日志链回溯
- 从事务的
lastLSN开始 - 通过
prevLSN字段回溯到事务开始 - 对每个日志记录执行 Undo
- 从事务的
- 执行 Undo 操作
- 读取日志记录的
oldValue - 将页恢复到修改前的状态
- 更新页的
PageLSN
- 读取日志记录的
- 写 CLR(Compensation Log Record)
- 记录撤销操作,写入补偿日志
- CLR 包含
undoNextLSN,指向下一个要撤销的日志 - 如果 Undo 过程中再次崩溃,可以从 CLR 继续
CLR 的作用:
classDiagram
class LogRecord {
+ LSN lsn
+ LogType type
+ Data oldValue
+ Data newValue
}
class CLR {
+ LSN lsn
+ LSN undoNextLSN
+ Data oldValue
+ Data newValue
}
LogRecord <|-- CLR : 继承
CLR 的优势:
- 可重复撤销:如果 Undo 过程中崩溃,可以从 CLR 继续
- 避免重复撤销:CLR 标记已经撤销的操作
- 支持部分回滚:可以只撤销事务的一部分
4. Fuzzy Checkpoint
4.1 Checkpoint 的作用
Checkpoint 是系统状态的快照,用于减少恢复时间:
graph TD
A[Checkpoint] --> B[记录事务表]
A --> C[记录脏页表]
A --> D[记录LSN]
B --> B1[活跃事务列表]
C --> C1[脏页及其recLSN]
D --> D1[Checkpoint LSN]
E[恢复时] --> F[从Checkpoint开始]
F --> G[减少日志扫描范围]
style A fill:#e3f2fd
style E fill:#fff3e0
Checkpoint 的类型:
- Fuzzy Checkpoint(模糊检查点)
- 不停止系统运行
- 记录当前状态,允许继续写入
- ARIES 采用的方式
- Sharp Checkpoint(清晰检查点)
- 停止所有写入
- 等待所有脏页刷盘
- 恢复简单但影响性能
4.2 Fuzzy Checkpoint 的实现
Fuzzy Checkpoint 允许在系统运行时进行:
sequenceDiagram
participant Checkpoint as Checkpoint Manager
participant Buffer as Buffer Pool
participant Log as Log File
Checkpoint->>Log: 写入BEGIN_CHECKPOINT
Checkpoint->>Checkpoint: 记录事务表快照
Checkpoint->>Checkpoint: 记录脏页表快照
Checkpoint->>Log: 写入END_CHECKPOINT
Checkpoint->>Log: 刷新日志到磁盘
Note over Checkpoint,Log: 系统继续正常运行
Fuzzy Checkpoint 的流程:
- 写入 BEGIN_CHECKPOINT
- 标记 Checkpoint 开始
- 记录当前 LSN
- 记录状态快照
- 记录事务表(活跃事务及其 lastLSN)
- 记录脏页表(脏页及其 recLSN)
- 不要求脏页刷盘
- 写入 END_CHECKPOINT
- 标记 Checkpoint 结束
- 将状态信息写入日志
- 刷新日志
- 确保 Checkpoint 信息持久化
- 系统继续正常运行
Fuzzy Checkpoint 的优势:
- 不阻塞写入:Checkpoint 过程中系统继续服务
- 减少恢复时间:只需从 Checkpoint 开始扫描日志
- 提高可用性:不影响系统性能
5. 部分回滚(Partial Rollback)
5.1 部分回滚的需求
部分回滚允许事务回滚到某个保存点(Savepoint):
graph TD
A[事务执行] --> B[Savepoint 1]
B --> C[操作1]
C --> D[操作2]
D --> E[Savepoint 2]
E --> F[操作3]
F --> G[操作4]
H[部分回滚] --> I[回滚到Savepoint 2]
I --> J[撤销操作3和4]
J --> K[保留操作1和2]
style A fill:#e3f2fd
style H fill:#fff3e0
style I fill:#e8f5e9
部分回滚的应用场景:
- 错误处理:遇到错误时回滚到保存点,继续执行
- 嵌套事务:支持子事务的回滚
- 灵活控制:提供更细粒度的事务控制
5.2 部分回滚的实现
部分回滚通过保存点和日志链实现:
sequenceDiagram
participant Txn as Transaction
participant Log as Log File
participant Buffer as Buffer Pool
Txn->>Log: 写入SAVEPOINT日志
Txn->>Txn: 记录savepointLSN
Txn->>Buffer: 执行操作
Buffer->>Log: 写入UPDATE日志
Txn->>Txn: 决定部分回滚
Txn->>Txn: 从lastLSN回溯到savepointLSN
Txn->>Buffer: 执行Undo操作
Buffer->>Log: 写入CLR
Txn->>Txn: 更新lastLSN为savepointLSN
部分回滚的步骤:
- 创建保存点
- 写入
SAVEPOINT日志记录 - 记录保存点的 LSN
- 写入
- 执行操作
- 继续执行事务操作
- 所有操作都记录在日志中
- 部分回滚
- 从当前
lastLSN回溯到保存点的 LSN - 对每个日志记录执行 Undo
- 写入 CLR 记录撤销操作
- 更新事务的
lastLSN为保存点的 LSN
- 从当前
6. ARIES 的工程实践
6.1 日志格式设计
ARIES 的日志记录包含以下信息:
classDiagram
class LogRecord {
+ LSN lsn
+ TransactionID transId
+ LogType type
+ PageID pageId
+ LSN prevLSN
+ Data oldValue
+ Data newValue
+ LSN undoNextLSN
}
class LogType {
<<enumeration>>
BEGIN
UPDATE
COMMIT
ABORT
CLR
CHECKPOINT
}
LogRecord --> LogType : 使用
日志记录的关键字段:
- lsn:日志序号,全局唯一
- transId:事务 ID,标识所属事务
- type:日志类型(BEGIN、UPDATE、COMMIT 等)
- pageId:修改的页 ID
- prevLSN:事务前一个日志的 LSN,构建日志链
- oldValue/newValue:修改前后的值,用于 Redo/Undo
- undoNextLSN:CLR 中指向下一个要撤销的日志
6.2 性能优化
ARIES 的优化策略:
graph TD
A[性能优化] --> B[批量日志写入]
A --> C[并行恢复]
A --> D[增量Checkpoint]
A --> E[日志压缩]
B --> B1[减少IO次数]
C --> C2[多线程恢复]
D --> D1[只记录变化]
E --> E1[减少日志量]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#f3e5f5
关键优化技术:
- 批量日志写入
- 将多个日志记录批量写入
- 减少磁盘 IO 次数
- 提高写入吞吐量
- 并行恢复
- Redo 阶段可以并行处理不同页
- Undo 阶段可以并行处理不同事务
- 提高恢复速度
- 增量 Checkpoint
- 只记录自上次 Checkpoint 以来的变化
- 减少 Checkpoint 的开销
- 提高系统可用性
- 日志压缩
- 合并多个小日志记录
- 减少日志量
- 降低存储成本
7. ARIES 的影响
7.1 对现代数据库的影响
ARIES 的设计思想被广泛采用:
graph TD
A[ARIES] --> B[商业数据库]
A --> C[开源数据库]
A --> D[存储引擎]
B --> B1[IBM DB2]
B --> B2[Oracle]
B --> B3[SQL Server]
C --> C1[PostgreSQL]
C --> C2[MySQL InnoDB]
D --> D1[RocksDB]
D --> D2[WiredTiger]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#f3e5f5
ARIES 的核心贡献:
- 页级 LSN:成为现代数据库的标准设计
- Redo/Undo 分离:提供了灵活的恢复机制
- Fuzzy Checkpoint:平衡了性能和恢复时间
- 部分回滚:提供了更细粒度的事务控制
7.2 设计原则总结
ARIES 的成功体现了几个重要的设计原则:
- 幂等性设计
- Redo 操作是幂等的,可以安全重复执行
- 简化了恢复逻辑,提高了可靠性
- 状态可重建
- 通过日志可以完全重建系统状态
- 不依赖内存状态,支持完全恢复
- 性能与可靠性平衡
- Fuzzy Checkpoint 减少恢复时间
- 条件 Redo 避免不必要的操作
- 在性能和可靠性之间找到平衡
- 工程化实现
- 考虑了实际系统的复杂性
- 支持并发、细粒度锁等高级特性
- 提供了完整的恢复方案
8. 总结
ARIES 是数据库恢复算法的经典设计,其核心思想包括:
- WAL 机制:先写日志再写数据,保证持久性
- LSN 关联:通过 LSN 关联页和日志,支持精确恢复
- Redo/Undo 分离:分别处理已提交和未提交事务
- 三阶段恢复:Analysis、Redo、Undo 三个阶段各司其职
- Fuzzy Checkpoint:支持增量检查点,减少恢复时间
- 部分回滚:支持保存点和部分回滚,提供灵活控制
ARIES 的设计思想影响了大量现代数据库系统,成为数据库恢复机制的标准参考。理解 ARIES 的设计原理,有助于理解现代数据库系统的恢复机制和工程实践。