写在前面
承接前几篇,本文聚焦"日志与崩溃恢复"——这是数据库 D(持久性)的实现核心。
本文要回答:
Redo Log、Undo Log、Binlog、WAL、Archive Log、Transaction Log——这么多日志都是干嘛的?数据库崩溃后是怎么自动恢复的?为什么 Oracle 重启很快但 MySQL 重启有时很慢?
一、ARIES 协议
1.1 数据库恢复的理论基础
ARIES(Algorithm for Recovery and Isolation Exploiting Semantics)是 1992 年 IBM 提出的恢复算法,所有现代关系数据库都基于它(变体)。
1.2 三阶段恢复
1
2
3
4
5
6
7
8
|
ARIES 三阶段:
1. Analysis(分析):扫描日志,重建事务表 + 脏页表
2. Redo(重做):重放所有"已提交但未刷盘"的修改
3. Undo(回滚):回滚所有"未提交但已写盘"的修改
关键概念:
- LSN(Log Sequence Number):日志序号,全局递增
- WAL 原则:数据页刷盘前,对应日志必须先刷盘
|
1.3 完整流程示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
时间线:
T1: BEGIN
T1: UPDATE balance SET v = 100 WHERE id = 1;
- 写 Undo Log: id=1, old_v = 50
- 写 Redo Log: id=1, new_v = 100
T2: BEGIN
T2: UPDATE balance SET v = 200 WHERE id = 2;
- 写 Undo Log: id=2, old_v = 80
- 写 Redo Log: id=2, new_v = 200
T1: COMMIT
- 写 COMMIT Log
- fsync Redo Log
[崩溃]
重启后:
Analysis:
- T1 已提交(看到 COMMIT)
- T2 未提交(没有 COMMIT)
Redo(从某个 LSN 开始):
- 重做 T1: id=1 改成 100
- 重做 T2: id=2 改成 200
Undo:
- 回滚 T2: 用 Undo Log 改回 id=2 = 80
最终:
- id=1: 100(T1 已提交)
- id=2: 80(T2 已回滚)
|
二、Redo Log vs Undo Log
2.1 物理日志 vs 逻辑日志
1
2
3
4
5
6
7
8
9
10
11
12
13
|
物理日志(Physical Log):
- 记录"页 X 偏移 Y 改成值 Z"
- Redo Log 都是物理日志(或物理到逻辑混合)
- 重放时精确还原页面
逻辑日志(Logical Log):
- 记录"事务 T 在表 t 改了什么"(行级、SQL 级)
- Undo Log 多为逻辑日志
- Binlog 是逻辑日志
为什么 Redo 必须物理?
- 重放时不能假设页处于某种状态
- 物理日志幂等(重复执行结果一致)
|
2.2 Redo Log 作用
1
2
3
4
5
6
7
8
|
1. 崩溃恢复:重做未刷盘的已提交事务
2. 加速 COMMIT:顺序写 Redo 比"刷数据页"快得多
3. 支持 PITR(Point-in-Time Recovery,配合归档日志)
特点:
- 物理日志
- 顺序追加(部分循环覆盖)
- 大小固定(一组文件)
|
2.3 Undo Log 作用
1
2
3
4
5
6
7
8
|
1. 事务回滚:失败时撤销修改
2. MVCC:保留旧版本供并发读
3. 崩溃恢复:回滚未提交事务
特点:
- 逻辑日志("改回什么值")
- 通常顺序追加
- 大小动态(不固定)
|
三、四大数据库的日志体系
3.1 Oracle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
日志类型:
- Redo Log(在线 + 归档)
- Undo Segment(独立表空间)
文件结构:
online redo log:3+ 组,每组 1+ 文件,循环写
redo01.log → redo02.log → redo03.log → 覆盖 redo01.log ...
archive log:归档模式(ARCHIVELOG)下,被覆盖前复制到归档位置
关键参数:
LOG_BUFFER:Redo Log Buffer 大小
LOG_ARCHIVE_DEST_n:归档目的地
FAST_START_MTTR_TARGET:崩溃恢复目标时间
|
3.2 SQL Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
日志类型:
- Transaction Log(.ldf 文件)
- 同时承担 Redo 和 Undo(没有独立 Undo Log)
特点:
- 单一日志(不像其他三家分开)
- 通过版本号在日志中区分 Redo/Undo 部分
- 数据库每个都有独立的 .ldf
工作流程:
1. 修改数据 → 写 Log Buffer
2. COMMIT → flush Log Buffer 到 .ldf
3. 后台 Checkpoint 刷数据页
4. 崩溃恢复:扫 .ldf 重做 / 回滚
关键参数:
recovery interval:目标恢复时间
target_recovery_time:每库目标恢复时间(秒)
|
3.3 MySQL InnoDB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
日志类型:
- Redo Log(ib_redo*,8.0+ 改名)
- Undo Log(undo tablespace)
- Binlog(Server 层,独立于引擎)
为什么要三套?
- Redo + Undo:引擎内部崩溃恢复
- Binlog:主从复制 + 备份
- 内部 XA 协调 Redo 和 Binlog
工作流程(详见第 4 篇):
1. 改 Buffer Pool
2. 写 Undo Log
3. PREPARE Redo Log(fsync)
4. 写 Binlog(fsync)
5. COMMIT Redo Log(fsync)
关键参数:
innodb_flush_log_at_trx_commit:Redo 刷盘策略(0/1/2)
sync_binlog:Binlog 刷盘策略(0/1/N)
innodb_log_file_size:单个 Redo Log 文件大小
innodb_log_files_in_group:Redo Log 文件数
|
3.4 PostgreSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
日志类型:
- WAL(Write-Ahead Log,pg_wal/ 目录)
- 没有 Undo Log(多版本直接存在 Heap)
特点:
- 单一日志(WAL)
- 物理日志为主(8.0+ 部分逻辑)
- 8.0+ 支持 logical replication(基于逻辑日志)
工作流程:
1. 改 Buffer(标记脏页)
2. 写 WAL Buffer
3. COMMIT → fsync WAL Buffer
4. 后台 Checkpoint 刷脏页
关键参数:
wal_level:minimal / replica / logical
synchronous_commit:on / off / remote_write / remote_flush
max_wal_size:WAL 大小上限(自动 checkpoint)
wal_keep_size:为流复制保留的 WAL 大小
|
3.5 横向对比
| 维度 |
Oracle |
SQL Server |
MySQL |
PostgreSQL |
| Redo |
Redo Log |
Transaction Log |
Redo Log |
WAL |
| Undo |
Undo Segment |
(在 .ldf 中) |
Undo Log |
Heap 内(旧 tuple) |
| 归档 |
Archive Log |
Log Backup |
Binlog |
WAL Archive |
| 复制 |
Redo Stream |
Transaction Log |
Binlog |
WAL Stream |
四、Checkpoint
4.1 为什么需要 Checkpoint
1
2
3
4
5
6
7
8
9
10
|
没有 Checkpoint 的问题:
- 崩溃恢复时,需要从头扫描所有 Redo Log
- Log 文件越来越大 → 恢复时间越来越长
- 不可能接受
Checkpoint 的作用:
- 定期把所有脏页刷盘
- 在 Redo Log 中记录"Checkpoint LSN"
- 崩溃恢复时从 Checkpoint LSN 开始扫
- 缩短恢复时间(MTTR)
|
4.2 工作机制
1
2
3
4
5
6
7
8
9
|
Checkpoint 流程:
1. 记录当前 Redo Log LSN(记为 C)
2. 把 Buffer Pool 中所有"LSN ≤ C"的脏页刷盘
3. 在 Redo Log 中写入 Checkpoint 记录
4. 更新控制文件中的"最新 Checkpoint LSN"
崩溃恢复:
- 从 Checkpoint LSN 开始扫 Redo Log
- 之前的数据已经持久化,不需要重做
|
4.3 各家的 Checkpoint
| 数据库 |
触发机制 |
关键参数 |
| Oracle |
基于时间(MTTR) |
FAST_START_MTTR_TARGET、FAST_START_IO_TARGET |
| SQL Server |
基于时间 + LSN |
recovery interval、indirect checkpoint |
| MySQL InnoDB |
基于 LSN 上限 |
innodb_max_dirty_pages_pct、innodb_io_capacity |
| PostgreSQL |
基于 WAL 大小 + 时间 |
max_wal_size、checkpoint_timeout |
1
2
3
4
5
6
7
|
-- MySQL:控制 Checkpoint 速度
SET GLOBAL innodb_max_dirty_pages_pct = 75;
SET GLOBAL innodb_io_capacity = 2000;
-- PostgreSQL:调整 Checkpoint 频率
ALTER SYSTEM SET max_wal_size = '4GB';
ALTER SYSTEM SET checkpoint_timeout = '15min';
|
4.4 Fuzzy Checkpoint
1
2
3
4
5
6
7
8
9
|
现代数据库都用"Fuzzy Checkpoint":
- 不停服
- 不冻结写操作
- 后台慢慢刷脏页
- 用户无感
代价:
- 需要"两阶段 Checkpoint"(开始/结束 LSN)
- 恢复时小范围可能重复 Redo(可接受)
|
五、崩溃恢复全过程
5.1 通用流程
1
2
3
4
5
6
7
8
9
10
11
|
1. 读取控制文件,找到最新 Checkpoint LSN
2. 从该 LSN 开始扫 Redo Log
- 重建事务表(哪些事务 BEGIN / COMMIT / ROLLBACK)
- 重建脏页表(哪些页被修改过)
3. Redo 阶段
- 重放 Checkpoint 之后的所有修改
- 已提交 + 未提交都重做(保证页面状态)
4. Undo 阶段
- 扫描未提交事务的 Undo Log
- 回滚这些事务的修改
5. 数据库进入正常状态,开始接受连接
|
5.2 影响恢复时间的因素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
1. Checkpoint LSN 到崩溃时刻的距离
- 距离越远,Redo 量越大
- 调小 Checkpoint 间隔可以缩短恢复时间
2. 未提交事务的数量
- 长事务多 → Undo 量大
- 长事务是恢复性能杀手
3. 磁盘随机 I/O 性能
- 恢复涉及大量随机读(读脏页)
- SSD 比 HDD 恢复快 10~100 倍
4. 并行恢复能力
- Oracle:多线程并行 Redo / Undo
- PG:单线程,慢
- MySQL:InnoDB 8.0+ 部分并行
|
5.3 实战:恢复时间监控
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-- Oracle:看上次恢复的预估时间
SELECT estimated_mttr FROM v$instance_recovery;
-- MySQL:InnoDB 恢复日志
-- 在 mysqld 启动日志中查看:
-- "InnoDB: Doing recovery: scanned X log"
-- "InnoDB: Database was not shutdown normally"
-- PostgreSQL:在启动日志中
-- "starting archive recovery"
-- "redo starts at X"
-- "redo done at Y"
-- "recovery stopping after commit timestamp Z"
|
六、刷盘策略与"双一"标准
6.1 刷盘时机
1
2
3
4
5
|
每次写文件系统的"写"操作其实分两步:
1. 写 OS Page Cache(write())
2. 刷到磁盘(fsync())
数据库的"刷盘"通常指 fsync()。
|
6.2 MySQL 的双一标准
1
2
3
4
5
6
7
8
9
10
11
12
13
|
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
含义:
- innodb_flush_log_at_trx_commit = 1
每次 COMMIT 都 fsync Redo Log → D(持久性)保证
- sync_binlog = 1
每次事务都 fsync Binlog → 复制一致性保证
为什么叫"双一"?
- 两个参数都 = 1
- 金融场景必须
- 性能代价:每事务多 1~2 次 fsync
|
6.3 不同级别权衡
1
2
3
4
5
6
7
8
9
|
innodb_flush_log_at_trx_commit:
0 = 每秒 fsync(崩溃丢最多 1 秒事务)→ 性能好
1 = 每次 fsync(永不丢)→ 默认、最安全
2 = 每次 COMMIT 写 OS Cache,每秒 fsync(OS 崩溃丢,DB 崩溃不丢)
sync_binlog:
0 = 由 OS 决定刷盘时机
1 = 每次事务都 fsync → 默认(5.7+)
N = 每 N 个事务 fsync 一次
|
6.4 PostgreSQL 的对应
1
2
3
4
5
6
|
synchronous_commit = on -- 等于 innodb_flush_log_at_trx_commit = 1
synchronous_commit = off -- 等于 0
synchronous_commit = local -- 类似 1(单机场景)
wal_sync_method = fsync -- 刷盘方法
wal_writer_delay = 200ms -- WAL Writer 后台刷盘间隔
|
七、Binlog 详解
7.1 Binlog 是什么
1
2
3
4
5
6
7
8
|
MySQL Server 层的逻辑日志
- 记录"所有已提交事务的修改"
- 用于主从复制、PITR、CDC(变更数据捕获)
Binlog 格式:
- STATEMENT:记录 SQL 语句(小,但有些函数 / NOW() 不安全)
- ROW:记录每行的修改(大,但安全) ← 推荐
- MIXED:自动选择
|
7.2 Binlog vs Redo Log
| 维度 |
Redo Log |
Binlog |
| 层级 |
引擎层(InnoDB) |
Server 层 |
| 内容 |
物理日志(页/偏移) |
逻辑日志(行变更) |
| 写入 |
循环覆盖 |
追加写,文件按序号 |
| 用途 |
崩溃恢复 |
复制、备份、CDC |
| 大小 |
固定(几个 G) |
一直增长 |
7.3 为什么需要内部 XA
1
2
3
4
5
6
7
8
9
10
11
|
场景:Redo Log 已写但 Binlog 还没写时崩溃
- 重启后:InnoDB 看到 PREPARE 状态,回滚事务
- 但如果该事务的 Redo 已经被从库消费(不可能,因为还没 COMMIT)...
实际场景:Redo 写完、Binlog 写了一半时崩溃
- 重启后:扫 Redo 找到 PREPARE 事务
- 检查 Binlog 是否完整:
* 完整 → 重做(保证从库能消费)
* 不完整 → 回滚(保证与从库一致)
→ 内部 XA 协议保证 Redo + Binlog 的一致性
|
八、归档与 PITR
8.1 为什么需要归档
1
2
3
4
5
6
7
8
9
10
11
|
Redo Log / WAL 是循环写的,会被覆盖
- 在线 Redo 容量有限(通常几个 G)
- 几小时前的修改可能已经丢失
归档(Archive):
- 把"将要被覆盖"的 Redo / WAL 复制到归档目录
- 归档文件永久保存(直到备份策略清理)
- 用于:
* 物理备份恢复到任意时间点(PITR)
* 增量备份基础
* 流复制从库延迟追赶
|
8.2 各家的归档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Oracle:Archive Log
- 数据库必须开 ARCHIVELOG 模式
- 后台进程 ARCn 自动归档
- RMAN 备份 / 恢复工具
SQL Server:Transaction Log Backup
- 数据库必须 FULL 或 BULK_LOGGED 恢复模式
- 主动执行 BACKUP LOG 命令
MySQL:Binlog(自动归档)
- 默认 binlog 一直保留
- binlog_expire_logs_seconds 控制保留时间
PostgreSQL:WAL Archive
- archive_mode = on
- archive_command = 'cp %p /backup/%f'
- 11+ 支持 replication slot + 物理备份
|
8.3 PITR(Point-in-Time Recovery)
1
2
3
4
5
6
7
8
9
10
11
12
|
场景:今天 14:00 误删了重要表,需要恢复到 13:59:59
流程:
1. 用一周前的全量备份恢复(base restore)
2. 应用归档日志,重放到目标时间点
3. 用 RESETLOGS / 类似命令打开数据库
各家命令:
Oracle:RMAN RECOVER DATABASE UNTIL TIME "TO_DATE('2026-06-15 13:59:59')"
SQL Server:RESTORE LOG ... WITH STOPAT='2026-06-15 13:59:59'
MySQL:mysqlbinlog --stop-datetime='2026-06-15 13:59:59' binlog.* | mysql
PostgreSQL:recovery_target_time = '2026-06-15 13:59:59'
|
九、小结
本文学习了日志与崩溃恢复:
- ARIES 协议:Analysis → Redo → Undo 三阶段
- 物理日志(Redo)vs 逻辑日志(Undo/Binlog)
- 四大数据库的日志体系差异
- Checkpoint 机制与 Fuzzy Checkpoint
- 崩溃恢复全过程(影响 MTTR 的因素)
- MySQL 的"双一"标准(持久性 + 复制一致性)
- Binlog 与 Redo Log 的内部 XA 协调
- 归档与 PITR
1
2
3
4
|
记住三句话:
1. WAL 是数据库持久性的根基——数据页刷盘前必须先写日志
2. Checkpoint 是恢复时间的"分水岭"——之前的不重做
3. MySQL 同时有 Redo 和 Binlog 是历史包袱,但要靠内部 XA 保证一致
|
下一篇进入复制与高可用:MySQL MGR、PostgreSQL 流复制、SQL Server AlwaysOn、Oracle ADG/RAC,以及共识协议(Paxos / Raft)。