事务提交返回「成功」之后,机器立刻断电——钱到底算不算扣成功?从库会不会少一条流水?这些问题的答案不在 SQL 语义里,而在 redo / undo / binlog 三条日志如何协作。本篇把三条日志的职责、刷盘参数和内部两阶段提交讲透,这是计费库「不敢丢提交」的底子。
本文是 MySQL 内核系列的第 3 篇(redo / undo / binlog)。 全系列 5 篇:
- MySQL 内核(开篇)· InnoDB 存储结构与 B+ 树索引
- MySQL 内核 · 事务隔离、MVCC 与锁
- MySQL 内核 · redo log、undo log 与 binlog(本篇)
- MySQL 内核 · 执行计划与慢查询优化
- MySQL 内核 · 主从复制与高可用
一句话定位:redo 保证崩溃后已提交事务能重放到数据页;undo 支撑回滚与 MVCC;binlog 在 Server 层服务复制与时间点恢复;两者用内部 2PC 对齐「到底提交没有」。
TL;DR
- redo:InnoDB 物理 WAL(页内改动),循环写日志文件;提交时按参数决定是否
fsync;崩溃后用 redo 前滚已提交变更。 - undo:逻辑反向日志;既回滚未提交事务,又给 MVCC 版本链供货;后台 purge 回收。
- binlog:Server 层逻辑日志;主从复制、审计、点时间恢复靠它——不是 InnoDB 崩溃恢复的主通道。
- 内部两阶段提交:redo prepare → 写 binlog → redo commit,用 XID 对齐双日志,避免「引擎提交了复制没记」或反过来。
- 双 1:
innodb_flush_log_at_trx_commit=1+sync_binlog=1,计费库常见底线;靠组提交摊薄 fsync。 - binlog 格式:ROW(推荐)/ STATEMENT / MIXED;广告流水复制用 ROW。
- 组提交:多事务合并 fsync,是双 1 下抬 TPS 的正道,而不是关安全参数。
Table of contents
Open Table of contents
1. 为什么需要三条日志
一次预算 UPDATE 粗粒度路径:
- 改 Buffer Pool 里的数据页(变脏);
- 写 undo(旧值)以支持回滚与别人读旧版;
- 写 redo(物理增量)以便崩溃重放;
- 提交时写 binlog,供从库与恢复;
- 数据页本身可能稍后才刷盘。
若每次提交都把脏页同步落盘,吞吐扛不住;若只改内存不记日志,崩溃必丢数。WAL(先记日志再刷数据页) 是折中:日志顺序写很快,数据页随机刷可以异步。
三条日志层不同、用途不同:混为一谈是 crash-safe 与复制事故的温床。
2. redo log:物理 WAL
2.1 写什么
redo 记录「某个表空间、某页、页内偏移,改成了什么」一类物理增量(实现细节随版本演进,心智上按物理日志理解即可)。特点:
- 顺序追加到 redo 日志文件(可配置一组固定大小文件循环使用);
- 有 LSN(Log Sequence Number) 标记进度;
- checkpoint 推进表示「某个 LSN 之前的脏页已落到数据文件」。
2.2 崩溃恢复在干什么
开机后 InnoDB:
- 用 redo 重放需要恢复的已提交(及处于确定状态的)变更到数据页;
- 结合 undo 回滚未提交事务。
所以:提交成功且 redo 已按策略落盘,即使脏页没刷,重启也能把修改找回来。
2.3 innodb_flush_log_at_trx_commit
| 值 | 行为 | 风险 |
|---|---|---|
| 1 | 每事务提交 fsync redo | 最安全,fsync 贵 |
| 2 | 提交写 OS 缓存,约每秒 fsync | OS 崩溃可能丢约 1s |
| 0 | 约每秒写+刷 | mysqld 崩也可能丢约 1s |
生产上还有「看上去没改 flush 参数,但盘很慢」:双 1 下每次提交的 p99 会直接贴着 fsync 延迟。云盘突然变慢时,计费提交延迟先报警,别误判成死锁。
3. undo log:回滚与 MVCC 原料
undo 记的是逻辑上的「如何变回去」。用途:
- 事务
ROLLBACK; - MVCC 构造旧版本;
- 崩溃恢复时清理未提交。
旧版本不再被任何 ReadView 需要后,由 purge 线程回收。长事务 / 大查询拖住 ReadView,会导致 undo 堆积、历史链表变长、磁盘与 CPU 被拖垮——广告库里「一个大报表事务挂几小时」是经典事故诱因。
purge 跟不上时的症状:undo tablespace 膨胀、History list length 上涨、简单点查变慢。治理:
- 禁止超长只读事务占着连接不放;
- 大删除改成分批
DELETE … LIMIT并尽快提交; - 监控历史链表与 undo 空间,设告警阈值。
4. binlog:复制与时间点恢复
binlog 在 Server 层,引擎无关(InnoDB/其他都会记,若开启)。职责:
- 主从复制事件源(见 主从复制与高可用);
- 点时间恢复(backup + binlog replay);
- 部分审计 / CDC(配合 row 镜像)。
格式:
- STATEMENT:记 SQL,省空间,不确定函数/触发器易踩坑;
- ROW:记行前后镜像,安全、体积大——广告流水推荐;
- MIXED:能 statement 则 statement,否则 row。
sync_binlog:
- 1:每事务提交 fsync binlog(与双 1 搭档);
- 0 / N:批量刷,吞吐好,崩溃可能丢最后若干事务的 binlog 事件。
运维上还要管 binlog 保留与磁盘:计费库流水高峰一天几十 GB 并不稀奇。留太短则点时间恢复与延迟从库搭建会断档;留太长则磁盘被吃光反过来拖垮主库。和备份策略(全量频率 + binlog 窗口)一起设计。
开启 binlog 的表上「隐式提交」语句(如部分 DDL)会打断你以为的大事务边界——批量变更脚本要清楚这一点。
5. 内部两阶段提交:对齐 redo 与 binlog
崩溃恢复可以重放 redo;从库只认 binlog。若出现:
- 引擎已提交、binlog 没写上 → 主库有数据、从库永世没有;
- binlog 写了、引擎未提交 → 从库多出主库没有的行。
MySQL 用内部 XA 式两阶段把二者绑在同一 XID 上:
- Prepare:InnoDB redo 进入 prepare 并按策略刷盘;
- 写 binlog(含 XID)并按
sync_binlog刷盘; - Commit:InnoDB redo 标 commit。
崩溃后:有 prepare 无完整 binlog → 回滚;binlog 已有完整事件 → 提交引擎侧。这样 crash-safe replication 才立得住。
排障时若看到「主库有行、从库没有」或反过来,除了复制延迟与过滤规则,也要怀疑历史上是否有过异常关机 + 非双 1 或人为跳过事件。GTID 缺口、人工 SET sql_log_bin=0 写数据,都是复制分叉的高频人因。
2PC 解决的是「双日志对是否提交意见一致」,不是分布式跨库事务(那是另一套故事)。
6. 双 1 与组提交
计费/预算库优先双 1;用组提交与硬件(低延迟盘)换 TPS,而不是先关安全阀。
组提交(group commit):多个事务的 binlog/redo 刷盘合并成一次 fsync,双 1 下仍可抬高提交吞吐。调优顺序建议:
- 确认业务是否真需要双 1;
- 上足够快的存储与合理 redo 容量;
- 吃组提交红利(观察 binlog 组提交相关状态是否健康);
- 仍不够再考虑把非关键库降档——不要拿计费库试刀;
- 应用侧适当批量(多行一次事务)也能减少提交次数,但别组装成「超大事务」伤害复制。
与 分布式事务 Outbox 的关系:业务 Outbox 表与 binlog CDC 常一起做最终一致;那要求 binlog 可靠且与引擎一致,再次指向双 1 + ROW。
7. LSN、checkpoint 与 redo 容量
直觉上记三条:
- 写入越快,redo LSN 推进越快;
- 脏页刷盘跟上,checkpoint 才能推进,redo 空间才能回收循环用;
- 若脏页刷不动(磁盘慢、BP 太大猛写),redo 可能打满 → 系统停写——计费高峰「突然全部 UPDATE 卡住」不一定是锁,也可能是 redo / 刷脏跟不上。
运维侧:
- 给够
innodb_log_file_size/ redo 容量,但不是无限加大就好(恢复时间、刷脏策略都要配套); - 观察 checkpoint 年龄、待刷脏比例;
- 存储延迟(p99 fsync)直接决定双 1 下的提交延迟。
8. ROW 镜像与 CDC
ROW 格式下,binlog 带行前/后镜像(可配 binlog_row_image = FULL/MINIMAL)。广告侧常见用途:
- 从库复制;
- Canal / Debezium 一类 CDC 进数仓或搜推;
- 审计「谁把预算改成了多少」。
MINIMAL 省空间,但 CDC 消费方可能缺列;财务级流水建议想清楚再砍镜像。别在 statement 格式下对含 UUID()、NOW() 的语句指望主从绝对一致。
DDL 与 binlog:Online DDL 期间复制延迟、元数据锁等待也是广告配置大表加列的常见痛。变更窗口、ALGORITHM/LOCK 选项、以及是否先在从库演练,应写进变更单,而不是上线当晚才想起「从库落后了」。
9. 小结:三条日志怎么记
| 日志 | 层 | 主要回答的问题 |
|---|---|---|
| redo | InnoDB | 崩溃后已提交怎么回到数据页? |
| undo | InnoDB | 怎么回滚?别人怎么读旧版? |
| binlog | Server | 从库/CDC/时间点恢复怎么重放? |
排障口诀:丢提交先查 flush 参数与磁盘 fsync;主从不一致先查 2PC/双 1/是否跳过 binlog;undo 膨胀先查长事务。
下一篇转向「慢」的日常——执行计划与慢查询优化:EXPLAIN 怎么读、索引为何失效、JOIN 与深分页怎么改。
参考
- MySQL 官方文档. InnoDB Redo Log
- MySQL 官方文档. Binary Log
- MySQL 官方文档. Binary Logging Options and Variables:
sync_binlog等。 - MySQL Internal 文档 / 源码笔记:binlog 与 engine 的 XA 内部 2PC 流程。
- 《MySQL 运维内参》等对 crash-safe 与双 1 的实践总结。