Skip to content
Charles Shao
Go back

MySQL 内核 · redo log、undo log 与 binlog

views

事务提交返回「成功」之后,机器立刻断电——钱到底算不算扣成功?从库会不会少一条流水?这些问题的答案不在 SQL 语义里,而在 redo / undo / binlog 三条日志如何协作。本篇把三条日志的职责、刷盘参数和内部两阶段提交讲透,这是计费库「不敢丢提交」的底子。

本文是 MySQL 内核系列的第 3 篇(redo / undo / binlog)。 全系列 5 篇:

  1. MySQL 内核(开篇)· InnoDB 存储结构与 B+ 树索引
  2. MySQL 内核 · 事务隔离、MVCC 与锁
  3. MySQL 内核 · redo log、undo log 与 binlog(本篇)
  4. MySQL 内核 · 执行计划与慢查询优化
  5. MySQL 内核 · 主从复制与高可用

一句话定位:redo 保证崩溃后已提交事务能重放到数据页;undo 支撑回滚与 MVCC;binlog 在 Server 层服务复制与时间点恢复;两者用内部 2PC 对齐「到底提交没有」。

TL;DR

Table of contents

Open Table of contents

1. 为什么需要三条日志

一次预算 UPDATE 粗粒度路径:

  1. 改 Buffer Pool 里的数据页(变脏);
  2. undo(旧值)以支持回滚与别人读旧版;
  3. redo(物理增量)以便崩溃重放;
  4. 提交时写 binlog,供从库与恢复;
  5. 数据页本身可能稍后才刷盘。

若每次提交都把脏页同步落盘,吞吐扛不住;若只改内存不记日志,崩溃必丢数。WAL(先记日志再刷数据页) 是折中:日志顺序写很快,数据页随机刷可以异步

UPDATE 路径上 Buffer Pool、redo、事务提交与异步刷脏,以及 redo/undo/binlog 三分职责。

三条日志层不同、用途不同:混为一谈是 crash-safe 与复制事故的温床。

2. redo log:物理 WAL

2.1 写什么

redo 记录「某个表空间、某页、页内偏移,改成了什么」一类物理增量(实现细节随版本演进,心智上按物理日志理解即可)。特点:

2.2 崩溃恢复在干什么

开机后 InnoDB:

  1. 用 redo 重放需要恢复的已提交(及处于确定状态的)变更到数据页;
  2. 结合 undo 回滚未提交事务。

所以:提交成功且 redo 已按策略落盘,即使脏页没刷,重启也能把修改找回来。

2.3 innodb_flush_log_at_trx_commit

行为风险
1每事务提交 fsync redo最安全,fsync 贵
2提交写 OS 缓存,约每秒 fsyncOS 崩溃可能丢约 1s
0约每秒写+刷mysqld 崩也可能丢约 1s

生产上还有「看上去没改 flush 参数,但盘很慢」:双 1 下每次提交的 p99 会直接贴着 fsync 延迟。云盘突然变慢时,计费提交延迟先报警,别误判成死锁。

3. undo log:回滚与 MVCC 原料

undo 记的是逻辑上的「如何变回去」。用途:

旧版本不再被任何 ReadView 需要后,由 purge 线程回收。长事务 / 大查询拖住 ReadView,会导致 undo 堆积、历史链表变长、磁盘与 CPU 被拖垮——广告库里「一个大报表事务挂几小时」是经典事故诱因。

purge 跟不上时的症状:undo tablespace 膨胀、History list length 上涨、简单点查变慢。治理:

4. binlog:复制与时间点恢复

binlog 在 Server 层,引擎无关(InnoDB/其他都会记,若开启)。职责:

格式:

sync_binlog

运维上还要管 binlog 保留与磁盘:计费库流水高峰一天几十 GB 并不稀奇。留太短则点时间恢复与延迟从库搭建会断档;留太长则磁盘被吃光反过来拖垮主库。和备份策略(全量频率 + binlog 窗口)一起设计。

开启 binlog 的表上「隐式提交」语句(如部分 DDL)会打断你以为的大事务边界——批量变更脚本要清楚这一点。

5. 内部两阶段提交:对齐 redo 与 binlog

崩溃恢复可以重放 redo;从库只认 binlog。若出现:

MySQL 用内部 XA 式两阶段把二者绑在同一 XID 上:

  1. Prepare:InnoDB redo 进入 prepare 并按策略刷盘;
  2. 写 binlog(含 XID)并按 sync_binlog 刷盘;
  3. Commit:InnoDB redo 标 commit。

崩溃后:有 prepare 无完整 binlog → 回滚;binlog 已有完整事件 → 提交引擎侧。这样 crash-safe replication 才立得住。

排障时若看到「主库有行、从库没有」或反过来,除了复制延迟与过滤规则,也要怀疑历史上是否有过异常关机 + 非双 1 或人为跳过事件。GTID 缺口、人工 SET sql_log_bin=0 写数据,都是复制分叉的高频人因。

应用、InnoDB、binlog、磁盘之间 prepare → 写 binlog → commit 的时序,以及崩溃裁决规则。

2PC 解决的是「双日志对是否提交意见一致」,不是分布式跨库事务(那是另一套故事)。

6. 双 1 与组提交

双 1、折中、高吞吐三档刷盘参数的安全与性能对照。

计费/预算库优先双 1;用组提交与硬件(低延迟盘)换 TPS,而不是先关安全阀。

组提交(group commit):多个事务的 binlog/redo 刷盘合并成一次 fsync,双 1 下仍可抬高提交吞吐。调优顺序建议:

  1. 确认业务是否真需要双 1;
  2. 上足够快的存储与合理 redo 容量;
  3. 吃组提交红利(观察 binlog 组提交相关状态是否健康);
  4. 仍不够再考虑把非关键库降档——不要拿计费库试刀
  5. 应用侧适当批量(多行一次事务)也能减少提交次数,但别组装成「超大事务」伤害复制。

分布式事务 Outbox 的关系:业务 Outbox 表与 binlog CDC 常一起做最终一致;那要求 binlog 可靠且与引擎一致,再次指向双 1 + ROW。

7. LSN、checkpoint 与 redo 容量

直觉上记三条:

  1. 写入越快,redo LSN 推进越快
  2. 脏页刷盘跟上,checkpoint 才能推进,redo 空间才能回收循环用;
  3. 若脏页刷不动(磁盘慢、BP 太大猛写),redo 可能打满 → 系统停写——计费高峰「突然全部 UPDATE 卡住」不一定是锁,也可能是 redo / 刷脏跟不上。

运维侧:

8. ROW 镜像与 CDC

ROW 格式下,binlog 带行前/后镜像(可配 binlog_row_image = FULL/MINIMAL)。广告侧常见用途:

MINIMAL 省空间,但 CDC 消费方可能缺列;财务级流水建议想清楚再砍镜像。别在 statement 格式下对含 UUID()NOW() 的语句指望主从绝对一致。

DDL 与 binlog:Online DDL 期间复制延迟、元数据锁等待也是广告配置大表加列的常见痛。变更窗口、ALGORITHM/LOCK 选项、以及是否先在从库演练,应写进变更单,而不是上线当晚才想起「从库落后了」。

9. 小结:三条日志怎么记

日志主要回答的问题
redoInnoDB崩溃后已提交怎么回到数据页?
undoInnoDB怎么回滚?别人怎么读旧版?
binlogServer从库/CDC/时间点恢复怎么重放?

排障口诀:丢提交先查 flush 参数与磁盘 fsync;主从不一致先查 2PC/双 1/是否跳过 binlog;undo 膨胀先查长事务。

下一篇转向「慢」的日常——执行计划与慢查询优化:EXPLAIN 怎么读、索引为何失效、JOIN 与深分页怎么改。

参考


views
Share this post on:

Previous Post
特征工程系统化框架:能用哪些料、料从哪进、料坏了怎么报警
Next Post
MySQL 内核 · 事务隔离、MVCC 与锁