跳到正文

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 LogInnoDB修改前的旧值回滚、MVCC 版本链
Redo LogInnoDB物理修改(页、偏移、值)崩溃恢复
BinlogServer逻辑变更(ROW 事件或 SQL)主从复制、归档恢复
Relay LogServer(从库)主库 Binlog 的中继副本从库 SQL 线程回放
Error LogServer启动、运行、崩溃错误排障第一站
Slow Query LogServer超过 long_query_time 的 SQL慢查询分析
General Query LogServer所有连接和 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_commit1每次提交 fsync Redo最安全
0每秒刷一次崩溃最多丢 1 秒
2每次写 OS Cache,每秒 fsync主机崩溃丢 1 秒
sync_binlog1每次提交 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,太大则恢复变慢。

把一门技术讲透到既懂也会用