主题
Redo Log 与 Binlog 两阶段提交
一句话:Redo 是 InnoDB 的物理重做日志,Binlog 是 Server 层的逻辑变更日志;两阶段提交让两者要么都生效、要么都不生效,换来崩溃恢复与主从复制的一致,代价是每次提交多一次 fsync 和一次状态协调。
适用版本:MySQL 8.0(InnoDB)。8.0.30 起
innodb_log_file_size被innodb_redo_log_capacity取代;8.0 用binlog_expire_logs_seconds设置保留期,expire_logs_days已移除(8.2 起)。8.4 的变量与命令差异以官方文档为准。
为什么需要
只有 Redo:崩溃后能恢复本机数据,但没法把变更发给从库,也没法做基于时间点的归档恢复。 只有 Binlog:从库能重放,但 InnoDB 的数据页可能还没刷盘,崩溃后本机数据可能比 Binlog 少。
两个日志分属不同层,写入时机也不同,所以必须回答一个问题:崩溃发生在两者之间时,这个事务到底算不算提交?
核心机制
两层日志的分工
| 日志 | 层 | 内容 | 用途 |
|---|---|---|---|
| Undo Log | InnoDB | 修改前的旧值 | 回滚、MVCC 版本链 |
| Redo Log | InnoDB | 物理修改(页、偏移、值) | 崩溃恢复 |
| Binlog | Server | 逻辑变更(ROW 事件或 SQL) | 主从复制、归档恢复 |
| Relay Log | Server(从库) | 主库 Binlog 的中继副本 | 从库 SQL 线程回放 |
| Error Log | Server | 启动、运行、崩溃错误 | 排障第一站 |
| Slow Query Log | Server | 超过 long_query_time 的 SQL | 慢查询分析 |
| General Query Log | Server | 所有连接和 SQL | 只在排查时短开,生产不要常开 |
一次提交的链路
一次提交按时间穿过四个组件,三份持久化动作(Undo、Redo PREPARE、Binlog 刷盘)各有明确的先后顺序,脏页刷盘则完全不在这条关键路径上。
两阶段提交的关键不是「先写谁」,而是 Binlog 是否完整落盘 作为提交的判据:
- Binlog 完整:崩溃恢复时把 PREPARE 的事务改成 COMMIT,主库和从库都有这条变更。
- Binlog 不完整:回滚该事务,主库和从库都没有这条变更。
反证:如果先写 Binlog 再写 Redo,Binlog 有、Redo 没有,从库会重放出主库不存在的数据;反过来则会主库有、从库没有。
崩溃恢复
重启时 InnoDB 扫描 Redo:
- 没有 PREPARE 标记:事务未进入提交阶段,回滚。
- 有 PREPARE 标记:检查对应 Binlog 是否完整;完整则提交,不完整则回滚。
刷盘参数
| 参数 | 默认(8.0) | 行为 | 风险 |
|---|---|---|---|
innodb_flush_log_at_trx_commit | 1 | 每次提交 fsync Redo | 最安全 |
| 0 | 每秒刷一次 | 崩溃最多丢 1 秒 | |
| 2 | 每次写 OS Cache,每秒 fsync | 主机崩溃丢 1 秒 | |
sync_binlog | 1 | 每次提交 fsync Binlog | 最安全 |
| 0 | 由 OS 决定 | 可能丢多个事务 | |
| N | 每 N 次提交刷一次 | 最多丢 N 个事务 |
「双 1」是最安全的组合,代价是每次提交两次 fsync;高并发下靠组提交(group commit)把多个事务的 fsync 合并。
Redo 的容量与循环写
8.0.30 之前是固定数量的 ib_logfile;8.0.30 起 InnoDB 在数据目录的 #innodb_redo 下维护 32 个文件,总容量由 innodb_redo_log_capacity 控制,默认 104857600 字节(100 MiB),可在线调整。
容量太小 → Checkpoint 频繁推进 → 写性能抖动;容量太大 → 崩溃恢复要重放更久。按写入量和可接受的恢复时间定,不要照搬默认值。
最小上手
my.cnf(MySQL 8.0):
ini
[mysqld]
log_bin = mysql-bin
binlog_format = ROW
server_id = 1
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
binlog_expire_logs_seconds = 604800 # 7 天
innodb_redo_log_capacity = 1073741824 # 1 GiB,8.0.30+验证:
sql
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW VARIABLES LIKE 'sync_binlog';
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';
SHOW VARIABLES LIKE 'innodb_redo_log_capacity';
SHOW BINARY LOGS;
SHOW BINARY LOG STATUS; -- 8.2+;8.0 可用 SHOW MASTER STATUS生产动作
binlog_format=ROW(8.0 默认):Canal、闪回、行级恢复都依赖它;STATEMENT 在部分函数下主从不一致。binlog_expire_logs_seconds=2592000(8.0 默认 30 天):保留期要覆盖「全量备份周期 + 最长同步中断时间」;expire_logs_days在 8.2 起已移除,不要再配。innodb_redo_log_capacity=104857600(8.0.30+ 默认 100 MiB):生产按写入量调大;innodb_log_file_size已废弃,同时设置新参数时它会被忽略并告警,未设置时仅用于换算容量。innodb_flush_log_at_trx_commit=1+sync_binlog=1:金融类默认;允许丢 1 秒的场景才考虑 0/2。- 监控:
SHOW ENGINE INNODB STATUS\G看Log sequence number与Last checkpoint at的差距;差距长期接近容量,说明 Checkpoint 压力大。 - 清理 Binlog 用
PURGE BINARY LOGS BEFORE '...',不要直接删文件;删除前确认从库和 Canal 的位点已经越过。
边界
- 两阶段提交不是分布式事务:它只协调 Redo 和 Binlog 两个本地日志,不保证跨库、跨服务的原子性。
- 提交成功仍可能丢数据:持久性最终取决于刷盘参数;
innodb_flush_log_at_trx_commit=0/2或sync_binlog=0都会扩大丢失窗口。 - Redo 不是归档日志:它是循环写的,旧的会被覆盖;需要长期保留的是 Binlog。
- Binlog 不是审计日志:它记录数据变更,不记录 SELECT;审计要用 General Log、审计插件或业务日志。
- 大事务会放大一切:一个事务生成几 GB Binlog,会拖长提交、复制和 Canal 解析。
讲清楚
Redo 和 Binlog 分属两层:Redo 是 InnoDB 的物理日志,负责崩溃后把已提交但没刷盘的数据页重放出来;Binlog 是 Server 层的逻辑日志,负责主从复制和归档恢复。一次提交先让 Redo 进入 PREPARE 并刷盘,再写 Binlog,最后把 Redo 标成 COMMIT。崩溃恢复时看 Binlog 是否完整:完整就提交,不完整就回滚,这样主库和从库不会一个有一个没有。代价是每次提交两次 fsync,生产用双 1 换安全,用组提交换吞吐;Redo 容量在 8.0.30 后由 innodb_redo_log_capacity 控制,太小会频繁 Checkpoint,太大则恢复变慢。