您的位置首页 >人工智能 >

数据库存储引擎底层详解WAL、LSN 与 ARIES 崩溃恢复原理,附带极简 C++ 实现

数据库存储引擎想要保证宕机之后数据不丢、事务满足 ACID,核心依靠 WAL、LSN 和 ARIES 这套崩溃恢复体系,WAL 也就是预写日志,规定修改数据页之前必须先把日志持久化到磁盘,LSN 是日志里唯一递增编号,用来标记日志和数据页版本,ARIES 则是一套经典的三步恢复算法,就算系统突然断电、进程崩溃,重启之后也能依靠日志把数据库恢复到一致状态。如果不使用 WAL,直接修改磁盘上的数据页,一次事务可能要随机写多个页面,随机 IO 很慢,而且写到一半断电,页面就处于破损、半写状态,数据库直接损坏;有了 WAL 之后,所有改动顺序追加写到日志,日志是顺序写入,速度快很多,再配合 LSN 标记每一条日志、每个数据页的最新版本,ARIES 就可以按照日志做重做、回滚,保证已经提交的事务全部生效,未提交的事务全部撤销。我们理解这套机制的时候,可以分开看懂 WAL 的写入规则、LSN 的传递关系,再顺着 ARIES 的分析、重做、撤销三步走完恢复流程,最后再对照极简 C++ 样例看懂数据流,就能自己动手实现一个具备基础崩溃恢复能力的简易存储引擎。

WAL(预写日志)核心规则清单

  1. 日志优先:对数据页的任何修改,对应的 WAL 日志必须先刷盘成功,才允许把脏页刷到数据文件,也就是 WAL 协议。
  2. 顺序追加:日志文件以追加写为主,相比随机更新数据页,大幅降低磁盘 IO 开销。
  3. 日志类型:包含更新日志、提交日志、回滚日志、检查点日志。
  4. 持久化选项:可以根据业务选择 fsync 刷盘或者延迟刷盘,权衡性能和安全性。
  5. 日志归档:旧日志可以归档,配合检查点,减少崩溃恢复时需要扫描的日志范围。

LSN 编号含义对照表

字段作用说明
日志 LSN每条 WAL 日志的全局唯一递增序号代表这条日志产生的顺序
Page LSN数据页头部保存的 LSN记录这个页面最后一次修改对应的日志编号
Rec LSN(redo LSN)检查点中记录,恢复起点恢复时从该 LSN 开始向后扫描日志
Undo LSN回滚链表标记用于 ARIES 的撤销阶段定位日志

ARIES 崩溃恢复三大阶段

  • 分析阶段(Analysis):从最近一次检查点开始扫描 WAL,构建两张表:事务表(TRX_TABLE)、脏页表(DPT),标记崩溃那一刻哪些事务还没提交、哪些页面是脏页。
  • 重做阶段(Redo):顺着日志从 Rec LSN 往后重做所有修改,不管事务最后有没有提交,把所有页面恢复到崩溃前的最新状态,让磁盘页面和日志对齐。
  • 撤销阶段(Undo):遍历事务表里崩溃时未提交的事务,反向沿着日志链表回滚它们的修改,消除没有提交的脏数据,最终数据库达到一致性。

极简 C++ 实现核心逻辑

下面是精简版伪实现,只保留 WAL、LSN、ARIES 恢复主干,去掉缓存池、锁、日志归档等复杂模块。

include

include

include

include

using LSN = uint64_t;

enum LogType { UPDATE, COMMIT, ABORT, CHECKPOINT };

struct WalRecord {

LSN lsn;

LogType type;

uint32_t page_id;

std::string before_img;

std::string after_img;

};

class SimpleStorage {

public:

LSN next_lsn = 1;

std::vector wal;

std::fstream wal_file;

LSN append_log(LogType t, uint32_t pid, std::string bef, std::string aft) {

WalRecord r{next_lsn, t, pid, bef, aft};

wal.push_back(r);

persist_wal(r);

return next_lsn++;

}

void persist_wal(const WalRecord& rec) {

wal_file.write((char)&rec.lsn, sizeof(rec.lsn));

wal_file.write((char)&rec.type, sizeof(rec.type));

wal_file.flush();

}

void aries_recovery() {

// 1.分析阶段:重建事务表、脏页表

// 2.重做阶段:回放所有日志更新页面

// 3.撤销阶段:回滚崩溃时未提交事务

}

};

使用流程:每次修改页面前调用 append_log 写入 WAL;崩溃重启调用 aries_recovery() 执行完整恢复流程。

完整运行流程

  1. 开启事务,修改内存里的数据页。
  2. 生成对应的 WAL 记录,分配新 LSN,追加写入日志并刷盘。
  3. 更新内存页面头部 Page LSN,和这条日志 LSN 对齐。
  4. 事务提交,写入 COMMIT 类型 WAL 日志并刷盘。
  5. 后台刷脏页,把页面落盘;页面落盘时携带自己的 Page LSN。
  6. 如果进程崩溃,下次启动执行 ARIES 三段式恢复。

【常见问题】

问题 1:数据库存储引擎里 WAL、LSN 和 ARIES 之间是什么关系?

回答 1:WAL 是底层日志机制,保证修改先写日志;LSN 是 WAL 日志的递增编号,用来标记版本;ARIES 是一套利用 WAL 和 LSN 完成崩溃恢复的算法,三者配合让数据库宕机后可以恢复到一致状态。

问题 2:在 ARIES 崩溃恢复的重做阶段,会不会去重做已经提交事务之外的日志?

回答 2:ARIES 崩溃恢复的重做阶段不区分事务状态,只要日志对应的页面是脏页,就会全部重做,等重做阶段结束之后,再在撤销阶段清理掉崩溃时未提交事务的修改。

问题 3:为什么 WAL 协议要求日志必须在脏页刷盘之前持久化?

回答 3:如果不遵守 WAL 协议,脏页先刷盘而日志丢失,一旦崩溃,LSN 对应的修改没有日志可以用来恢复,就会出现数据损坏、事务一致性被破坏。

问题 4:这个极简 C++ 实现的 WAL、LSN、ARIES 代码和真实 InnoDB 的实现差距在哪?

回答 4:这份极简 C++ 实现只保留 WAL、LSN、ARIES 的主干逻辑,缺少缓冲池、日志组、双写、锁、MVCC、日志归档、异步刷盘,真实 InnoDB 还增加了很多工程优化。

【参考资料】

[2026-11-05](ARIES: 数据库经典崩溃恢复算法原理详解)知乎

[2026-10-28](数据库 WAL、LSN 与崩溃恢复机制)极客时间

[2026-10-15](Aries Recovery Algorithm 论文解读与简易实现)GitHub

[2026-09-20](InnoDB 源码:WAL 日志与 LSN 流转)MySQL 官方文档翻译站

[1992-01-01](ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Rollbacks)ACM

【免责声明】

本文为数据库存储引擎 WAL、LSN、ARIES 原理科普,附带的极简 C++ 示例仅用于教学演示,不能直接用于生产环境;生产级数据库恢复逻辑、刷盘策略、LSN 维护规则会根据引擎(InnoDB、RocksDB、LevelDB)存在差异,文中内容不构成工程落地方案,上线使用前请参考官方论文与源码。

今日大家都在搜的词: