SQL数据库的RedoLog(重做日志)是InnoDB等存储引擎实现事务持久性的关键机制,它会在事务执行过程中,将对数据页的修改操作按顺序记录到日志文件中,这些日志是崩溃恢复时的核心依据。

RedoLog的核心工作机制
RedoLog的工作遵循Write-Ahead Logging(预写日志)原则,即事务对数据页的修改必须先写入RedoLog,再更新到内存中的数据页,最后才会由后台线程异步刷到磁盘的数据文件。这个机制可以保证即使数据库突然崩溃,已经写入RedoLog的修改也不会丢失。
RedoLog文件是循环写入的,由两个或多个固定大小的文件组成,写入满一个后会切换到下一个,覆盖旧的日志前需要确保对应的脏页已经刷到磁盘。每条RedoLog记录包含事务ID、数据页地址、修改内容等信息,格式会根据操作类型有所不同。
RedoLog的写入流程
事务执行过程中,每次修改数据页时,存储引擎会先生成对应的RedoLog记录,写入到RedoLog Buffer(内存中的日志缓冲区),之后根据配置的刷盘策略,将Buffer中的内容写到操作系统的Page Cache,或者直接刷到磁盘的RedoLog文件中。
常见的刷盘策略有三种:
- 事务提交时不主动刷盘,依赖后台线程定期刷写,性能最高但风险最大
- 事务提交时将RedoLog Buffer写入Page Cache,由操作系统控制刷盘,是多数场景的默认配置
- 事务提交时直接将RedoLog刷到磁盘,安全性最高但性能开销最大
崩溃恢复的整体流程
当数据库异常崩溃后重启,会自动进入崩溃恢复流程,核心目标是将已提交事务的修改全部应用到数据页,同时回滚未提交的事务,让数据库回到一致的状态。整个恢复流程主要分为两个阶段:
第一阶段:RedoLog重做阶段
这个阶段会从头扫描RedoLog文件,找到最后一个检查点(Checkpoint)的位置,检查点是数据库记录的一个标记,代表该位置之前的所有脏页都已经刷到磁盘,不需要再重做对应的日志。
从检查点之后开始,按顺序重放所有RedoLog记录,将修改应用到内存中的数据页,这个过程是幂等的,即使同一条日志被重复执行,也不会产生错误的结果。重做完成后,所有已提交事务的修改都已经体现在内存的数据页中。
以下是模拟RedoLog重做的简化逻辑代码:
// 模拟RedoLog重做流程
public class RedoRecover {
// 检查点之后的RedoLog记录列表
private List<RedoLogRecord> redoLogs;
// 内存中的数据页缓存
private Map<String, DataPage> bufferPool;
public void redoPhase() {
// 找到最后一个检查点位置
int checkpointPos = findLastCheckpoint();
// 从检查点之后开始遍历RedoLog
for (int i = checkpointPos; i < redoLogs.size(); i++) {
RedoLogRecord record = redoLogs.get(i);
// 获取数据页地址
String pageId = record.getPageId();
DataPage page = bufferPool.get(pageId);
if (page == null) {
// 数据页不在内存中,先从磁盘加载
page = loadPageFromDisk(pageId);
bufferPool.put(pageId, page);
}
// 重放修改操作到数据页
page.applyModify(record.getModifyContent());
}
System.out.println("Redo阶段完成,已提交事务的修改全部重做");
}
// 模拟RedoLog记录结构
static class RedoLogRecord {
private String pageId;
private String modifyContent;
public String getPageId() { return pageId; }
public String getModifyContent() { return modifyContent; }
}
// 模拟数据页结构
static class DataPage {
private String pageId;
private String data;
public void applyModify(String modifyContent) {
// 实际逻辑是解析修改内容,更新数据页的对应字段
this.data = modifyContent;
}
}
private int findLastCheckpoint() { return 0; }
private DataPage loadPageFromDisk(String pageId) { return new DataPage(); }
}
第二阶段:UndoLog回滚阶段
重做阶段完成后,内存中的数据页已经包含了所有已提交和未提交事务的修改,接下来需要回滚所有未提交的事务。数据库会通过UndoLog(回滚日志)找到所有未提交的事务,反向执行这些事务的修改操作,将数据恢复到事务开始前的状态。
UndoLog本身也会记录对应的RedoLog,所以在回滚阶段之前,UndoLog的修改已经通过重做阶段被应用到数据页,不会出现回滚操作丢失的问题。
崩溃恢复的关键优化点
为了提升恢复效率,数据库会在运行过程中定期打检查点,记录当前已经刷盘的数据页位置,崩溃恢复时只需要从最后一个检查点之后的日志开始重做,不需要扫描全部RedoLog文件。同时,RedoLog的顺序写入特性也让日志扫描的效率非常高,不会出现随机读的性能问题。
另外,恢复过程中会先处理RedoLog再处理UndoLog,这个顺序可以保证回滚操作基于最新的数据状态执行,避免出现数据不一致的问题。整个过程对用户是透明的,恢复完成后数据库就可以正常对外提供服务。