MySQL redo log 与 binlog
redo log 和 binlog 都记录数据变化,但它们不是一回事。
一句话先记住:
redo log 解决“事务提交后,MySQL 宕机了,InnoDB 怎么把已提交的数据恢复回来”;binlog 解决“Server 层怎么记录这次变更,供主从复制、全量备份后的增量恢复、审计回放使用”。
如果只背“redo 是物理日志,binlog 是逻辑日志”,面试很容易卡住。真正要理解的是:MySQL 修改一行数据时,不会每次都把磁盘数据页立即刷回磁盘,而是先改内存 Buffer Pool,再写日志;redo log 保证 InnoDB 可以崩溃恢复,binlog 保证 MySQL Server 层可以把变更传出去和回放。
学习目标
| 目标 | 你需要掌握什么 |
|---|---|
| 知道是什么 | redo log、binlog 分别记录什么、属于哪一层 |
| 知道为什么 | 为什么不能只靠刷数据页保证持久性,为什么还需要 binlog |
| 知道怎么工作 | Buffer Pool、WAL、redo prepare、写 binlog、redo commit、崩溃恢复 |
| 知道不这样会怎样 | redo/binlog 不一致会导致主库恢复结果和从库回放结果不一致 |
| 会项目落地 | 能解释主从复制、备份恢复、大事务、刷盘参数、复制延迟 |
| 会面试 | 能回答两阶段提交、组提交、sync_binlog、innodb_flush_log_at_trx_commit |
先理解一次 update 做了什么
假设执行:
update account
set balance = balance - 100
where id = 1;InnoDB 不是直接找到磁盘文件,把那一页立刻改掉并刷盘。更真实的过程是:
flowchart TD
A["客户端执行 update"] --> B["Server 层解析和优化 SQL"]
B --> C["InnoDB 找到数据页"]
C --> D{"数据页是否在 Buffer Pool"}
D -- "不在" --> E["从磁盘读取页到 Buffer Pool"]
D -- "已在" --> F["直接使用内存页"]
E --> G["修改 Buffer Pool 中的数据页"]
F --> G
G --> H["生成 undo log"]
G --> I["生成 redo log"]
G --> J["Server 层生成 binlog event"]
I --> K["提交阶段协调 redo 和 binlog"]
J --> K
K --> L["返回提交成功"]这里要抓住三个关键词:
| 关键词 | 含义 |
|---|---|
| Buffer Pool | InnoDB 的内存缓存,数据页先在内存里修改 |
| WAL | Write-Ahead Logging,先写日志,再择机刷数据页 |
| 脏页 | 内存中的数据页已经修改,但还没刷到磁盘数据文件 |
为什么要这样设计?因为随机刷数据页很慢。数据页可能分散在磁盘文件不同位置,如果每次提交都强制刷数据页,写性能会很差。redo log 是顺序写,先把“这个页被怎么改了”记录下来,事务提交就可以先返回;后面再由刷脏页机制慢慢把 Buffer Pool 的脏页写回磁盘。
redo log 是什么
redo log 是 InnoDB 引擎层的重做日志,用于崩溃恢复。
它记录的是 InnoDB 数据页级别的修改信息,可以理解为:
某个表空间的某个数据页,在某个偏移位置,被改成了什么样。
不要把 redo log 理解成完整 SQL。redo log 面向的是 InnoDB 存储引擎,它关心的是数据页怎么恢复,而不是用户执行了哪条 SQL。
redo log 为什么能保证持久性
事务提交时,只要 redo log 按可靠策略写入磁盘,即使修改后的数据页还没有刷盘,MySQL 宕机后也可以通过 redo log 把数据页重新修好。
flowchart TD
A["事务修改 Buffer Pool 数据页"] --> B["数据页变成脏页"]
B --> C["写 redo log"]
C --> D{"redo log 是否已持久化"}
D -- "是" --> E["事务可以认为提交可靠"]
D -- "否" --> F["宕机可能丢失最近事务"]
E --> G["后台线程择机刷脏页"]
E --> H["宕机后用 redo log 重放恢复"]这就是 WAL 的核心:先把恢复所需的日志写安全,再慢慢刷真正的数据页。
如果没有 redo log,就会出现很麻烦的问题:
- 事务提交时必须强制刷大量随机数据页,性能很差。
- 如果只改了内存页,宕机后磁盘数据文件还是旧的,提交过的数据会丢。
- 数据页可能只刷了一半,恢复时不知道哪些页应该恢复到什么状态。
redo log 的几个关键概念
| 概念 | 解释 |
|---|---|
| redo log buffer | 内存中的 redo 日志缓冲区 |
| redo log file | 磁盘上的 redo 日志文件 |
| LSN | Log Sequence Number,日志序列号,用来标识日志推进位置 |
| checkpoint | 检查点,表示哪些 redo 对应的脏页已经刷到磁盘 |
| write | 把 redo 从用户态/内存写到操作系统缓存 |
| fsync | 强制把日志落到磁盘或持久化设备 |
redo log 文件是循环写的。旧日志对应的脏页已经刷盘后,这部分日志空间就可以复用;如果脏页迟迟刷不下去,redo 空间被追上,MySQL 就必须加快刷脏页,写入性能会抖动。
flowchart TD
A["redo log 文件组"] --> B["顺序写入新日志"]
B --> C["checkpoint 之前的日志可复用"]
B --> D["checkpoint 之后的日志仍可能用于恢复"]
D --> E{"写入点快追上 checkpoint"}
E -- "是" --> F["加快刷脏页"]
E -- "否" --> G["继续正常写入"]binlog 是什么
binlog 是 MySQL Server 层的二进制日志。它记录的是数据库层面的逻辑变更事件,主要用于:
- 主从复制。
- 全量备份之后的增量恢复。
- 数据误操作后的时间点恢复。
- 数据变更审计和同步到下游系统。
binlog 不属于 InnoDB,而属于 MySQL Server 层。因此即使使用其他存储引擎,也可以有 binlog。
binlog 的三种格式
| 格式 | 记录内容 | 优点 | 风险 |
|---|---|---|---|
| STATEMENT | 原始 SQL 语句 | 日志量小,可读性强 | 非确定性函数、触发器、执行计划差异可能导致主从不一致 |
| ROW | 每行修改前后数据 | 主从一致性最好,生产最常用 | 日志量较大 |
| MIXED | SQL 和行模式混合 | 尝试兼顾体积和一致性 | 判断规则增加理解成本 |
商业项目通常优先使用 ROW,尤其是订单、支付、库存、资产、采集任务这类不能接受主从不一致的系统。
查看格式:
show variables like 'binlog_format';redo log 与 binlog 对比
| 项目 | redo log | binlog |
|---|---|---|
| 所属层 | InnoDB 引擎层 | MySQL Server 层 |
| 核心目标 | 崩溃恢复,保证提交事务不丢 | 复制、备份恢复、审计回放 |
| 记录粒度 | 数据页物理修改 | SQL 事件或行变更事件 |
| 写入方式 | 循环写,空间可复用 | 追加写,一个文件写满切下一个 |
| 是否所有引擎都有 | 不是,主要是 InnoDB | 是 Server 层能力 |
| 是否用于主从复制 | 不直接用于复制 | 主从复制核心依据 |
| 是否用于崩溃恢复 | 是 | 不是 InnoDB 崩溃恢复主力 |
| 提交一致性 | 通过事务提交和两阶段提交 | 通过两阶段提交与 redo 对齐 |
简单记法:
| 你关心的问题 | 看哪个日志 |
|---|---|
| 宕机后提交事务会不会丢 | redo log |
| 从库为什么能同步主库变更 | binlog |
| 误删数据后如何恢复到某个时间点 | 全量备份 + binlog |
| 为什么长事务导致复制延迟 | binlog 大事务提交后从库回放慢 |
| 为什么写入突然抖动 | redo 空间、刷脏页、磁盘 fsync |
为什么不能只要 redo log
redo log 可以让 InnoDB 自己恢复,但它不适合做主从复制和跨系统回放。
原因包括:
- redo log 是 InnoDB 私有格式,和存储引擎强绑定。
- redo log 是循环写,旧日志会被覆盖,不适合作长期增量恢复。
- redo log 记录的是页级变化,不适合给从库或下游系统理解业务变更。
- MySQL Server 层需要一种统一日志来支持复制和恢复,这就是 binlog。
为什么不能只要 binlog
binlog 不能替代 InnoDB 的崩溃恢复。
原因包括:
- binlog 是 Server 层逻辑日志,不知道 InnoDB 数据页刷盘到了什么程度。
- 宕机恢复时,InnoDB 需要根据页 LSN、redo LSN 判断哪些页要重放。
- binlog 追加写,不负责 Buffer Pool 脏页恢复。
- 如果每次提交都靠刷数据页保证持久化,性能会很差。
所以两个日志都需要:redo 管引擎内部可靠性,binlog 管复制和外部恢复。
两阶段提交是什么
更新事务提交时,MySQL 要保证 redo log 和 binlog 在事务边界上一致。典型流程是:
sequenceDiagram
participant C as "Client"
participant S as "Server 层"
participant I as "InnoDB"
C->>S: "commit"
S->>I: "写 redo log prepare"
I-->>S: "prepare 完成"
S->>S: "写 binlog"
S->>I: "写 redo log commit"
S-->>C: "提交成功"这个过程叫两阶段提交。这里的两阶段不是分布式事务里的 XA 跨库两阶段提交,而是 MySQL Server 层协调 binlog 和 InnoDB redo log 的内部提交过程。
为什么需要两阶段提交
如果没有两阶段提交,redo log 和 binlog 可能只成功一个。
场景一:先写 redo,再写 binlog
flowchart TD
A["redo log 写成功"] --> B["MySQL 宕机"]
B --> C["binlog 没写成功"]
C --> D["主库崩溃恢复后有这次更新"]
C --> E["从库没有收到这次更新"]
D --> F["主从数据不一致"]
E --> F主库靠 redo 恢复出这次更新,但 binlog 没有记录,从库无法复制这次更新。
场景二:先写 binlog,再写 redo
flowchart TD
A["binlog 写成功"] --> B["MySQL 宕机"]
B --> C["redo log 没写成功"]
C --> D["主库崩溃恢复后没有这次更新"]
C --> E["从库可能根据 binlog 回放了这次更新"]
D --> F["主从数据不一致"]
E --> F从库按 binlog 回放了更新,但主库恢复后没有这次更新,也会不一致。
两阶段提交的目的就是让 MySQL 崩溃恢复时能判断:
| 崩溃位置 | 恢复判断 |
|---|---|
| redo prepare 前崩溃 | 事务没有准备成功,回滚 |
| redo prepare 后、binlog 前崩溃 | binlog 不存在,回滚 |
| binlog 写完、redo commit 前崩溃 | binlog 存在,提交 |
| redo commit 后崩溃 | 事务已提交 |
更口语化地说:redo 先进入 prepare 状态,等 binlog 写成功后,再把 redo 标记为 commit。崩溃恢复时,如果发现 redo 是 prepare,就去检查对应 binlog 是否完整;binlog 完整则提交,否则回滚。
一次事务提交的完整链路
flowchart TD
A["执行 update"] --> B["生成 undo log"]
B --> C["修改 Buffer Pool 数据页"]
C --> D["生成 redo log 到 redo buffer"]
D --> E["提交阶段"]
E --> F["redo log 写入 prepare"]
F --> G["binlog 写入文件"]
G --> H["redo log 写入 commit"]
H --> I["返回客户端成功"]
I --> J["后台刷脏页到数据文件"]这里 undo log 也很重要:它支持事务回滚和 MVCC 旧版本。undo log 本身的修改也需要 redo log 保护,否则宕机后连“怎么回滚、怎么找旧版本”都可能不可靠。MVCC 与 undo log 的关系见:MVCC。
崩溃恢复时怎么判断
MySQL 重启后,InnoDB 会扫描 redo log:
flowchart TD
A["MySQL 重启"] --> B["扫描 redo log"]
B --> C{"事务 redo 状态"}
C -- "commit" --> D["重放 redo,保证数据页恢复"]
C -- "prepare" --> E["检查 binlog 是否完整"]
E -- "完整" --> D
E -- "不完整" --> F["回滚事务"]
C -- "未完成" --> F这就是为什么 redo 和 binlog 要一起理解。redo 决定 InnoDB 能不能恢复数据页,binlog 决定 prepare 状态的事务是否已经完成 Server 层提交。
刷盘参数怎么影响可靠性
innodb_flush_log_at_trx_commit
这个参数控制 redo log 的刷盘策略。
| 值 | 行为 | 可靠性 | 性能 |
|---|---|---|---|
| 0 | 每秒写入并刷盘 redo | 宕机可能丢 1 秒事务 | 最好 |
| 1 | 每次事务提交都写入并刷盘 redo | 最可靠 | 成本最高 |
| 2 | 每次提交写入 OS cache,每秒刷盘 | 操作系统崩溃可能丢 1 秒 | 折中 |
生产核心交易系统通常用 1。如果是日志采集、临时统计、可重放数据,可以结合业务容忍度评估。
sync_binlog
这个参数控制 binlog 的刷盘策略。
| 值 | 行为 | 风险 |
|---|---|---|
| 0 | 由操作系统决定何时刷盘 | 机器宕机可能丢 binlog |
| 1 | 每次事务提交都刷 binlog | 最可靠,性能成本高 |
| N | 每 N 次提交刷一次 | 最多可能丢 N 个事务左右的 binlog |
核心系统常见组合:
set persist innodb_flush_log_at_trx_commit = 1;
set persist sync_binlog = 1;这组配置可靠性高,但写入吞吐会受磁盘 fsync 能力影响。高并发写入场景会结合 SSD、组提交、事务拆分、批量写入来优化。
组提交是什么
如果每个事务都单独 fsync 一次 redo 和 binlog,磁盘压力会很大。MySQL 会尽量把一批同时提交的事务组合起来,统一写日志和刷盘,这叫组提交。
flowchart TD
A["多个事务几乎同时 commit"] --> B["进入提交队列"]
B --> C["合并写 binlog"]
C --> D["一次或少量 fsync"]
D --> E["批量完成提交"]组提交的意义是:在保证事务顺序和一致性的前提下,减少 fsync 次数,提高吞吐。
但大事务会破坏这个效果。一个事务里更新几十万行,binlog 和 redo 都很大,从库回放也慢,线上常见现象就是提交抖动、复制延迟、备份恢复时间变长。
和 undo log 的区别
redo、undo、binlog 经常一起出现,但职责完全不同:
| 日志 | 主要作用 | 典型问题 |
|---|---|---|
| redo log | 崩溃后重做已提交修改 | 提交后宕机为什么不丢 |
| undo log | 回滚事务、提供 MVCC 旧版本 | 回滚怎么恢复,快照读怎么看旧值 |
| binlog | 复制、备份恢复、审计 | 从库怎么同步,误删怎么恢复 |
执行 update 时通常会同时涉及它们:
- undo log 记录修改前的旧值,回滚和 MVCC 要用。
- redo log 记录页修改,宕机恢复要用。
- binlog 记录 Server 层变更事件,复制和恢复要用。
更细一点:undo log 存在于 InnoDB 的 undo 表空间或回滚段中,它本身也是数据库页。对 undo 页的修改也会产生 redo log,所以 redo 保护的不只是业务数据页,也包括 undo 页等 InnoDB 内部页。
商业常用场景
主从复制
主库提交事务后写 binlog,从库 IO 线程拉取 binlog,SQL 线程或多线程复制回放 relay log。
flowchart TD
A["主库提交事务"] --> B["写 binlog"]
B --> C["从库 IO 线程拉取"]
C --> D["写 relay log"]
D --> E["从库 SQL 线程回放"]
E --> F["从库数据追上主库"]复制延迟常见原因:
| 原因 | 解释 |
|---|---|
| 主库大事务 | 从库必须回放大量行变更 |
| 从库慢 SQL | 回放速度低于主库写入速度 |
| 从库硬件弱 | CPU、IO、磁盘能力不足 |
| 锁等待 | 从库回放线程被查询或事务阻塞 |
| 单表热点 | 并行复制难以充分拆分 |
误删数据恢复
常见恢复思路:
- 找到最近一次全量备份。
- 恢复到临时库。
- 用 binlog 回放到误操作前的时间点。
- 校验数据后,把缺失数据补回生产。
redo log 不能替代这个过程,因为 redo 是循环写且面向 InnoDB 内部恢复,不适合做长期时间点恢复。
订单和支付系统
订单、支付、库存这类系统通常要求:
binlog_format=ROW,减少主从不一致风险。sync_binlog=1,降低 binlog 丢失风险。innodb_flush_log_at_trx_commit=1,保证提交事务尽量不丢。- 避免超大事务,降低提交抖动和复制延迟。
- 关键表变更需要审计或同步下游时,基于 binlog 做 CDC。
SQL Demo:查看日志配置
查看 binlog 是否开启:
show variables like 'log_bin';
show variables like 'binlog_format';
show binary logs;
show master status;MySQL 8.0.22 之后更推荐:
show binary log status;查看可靠性相关参数:
show variables like 'sync_binlog';
show variables like 'innodb_flush_log_at_trx_commit';查看 redo 相关指标:
show engine innodb status\G在输出中关注 LOG 段,例如 LSN、checkpoint、pending writes 等信息。不同 MySQL 版本字段会有差异,学习时重点看日志推进和刷盘压力。
SQL Demo:观察 binlog 事件
开启 binlog 的环境中,可以先查看当前文件:
show binary log status;执行一个小事务:
create table if not exists log_demo_account (
id bigint primary key,
balance int not null
) engine = InnoDB;
insert into log_demo_account(id, balance)
values (1, 1000)
on duplicate key update balance = values(balance);
begin;
update log_demo_account
set balance = balance - 100
where id = 1;
commit;再用命令行查看 binlog:
mysqlbinlog --base64-output=decode-rows -vv mysql-bin.000001如果是 ROW 格式,你会看到表行变更事件,而不是简单的一条原始 SQL。这能帮助你理解为什么 ROW 更适合保证复制一致性。
生产排查流程
主从延迟
flowchart TD
A["发现主从延迟"] --> B["查看主库是否有大事务"]
B --> C["查看从库复制线程状态"]
C --> D["检查从库锁等待和慢 SQL"]
D --> E["检查磁盘 IO 和 CPU"]
E --> F["评估拆分事务或开启并行复制"]常用命令:
show replica status\G;
show processlist;
select *
from information_schema.innodb_trx;老版本 MySQL 使用:
show slave status\G;写入抖动
flowchart TD
A["写入 RT 突然升高"] --> B["检查磁盘 fsync 延迟"]
B --> C["检查 redo checkpoint 压力"]
C --> D["检查大事务和批量更新"]
D --> E["检查 Buffer Pool 脏页比例"]
E --> F["优化事务大小和 IO 能力"]常见处理方向:
| 现象 | 处理方向 |
|---|---|
| 大事务提交慢 | 拆分批次,每批几百到几千行 |
| 从库延迟高 | 减少大事务,优化从库 SQL,开启并行复制 |
| fsync 慢 | 检查磁盘、云盘规格、文件系统、IO 饱和 |
| checkpoint 压力大 | 调整 redo 容量、刷脏页能力、写入峰值 |
常见误区
| 误区 | 正确理解 |
|---|---|
| redo log 和 binlog 都是 SQL 日志 | redo 面向 InnoDB 页恢复,binlog 才面向复制和回放 |
| 有 binlog 就不需要 redo | binlog 不能负责 Buffer Pool 脏页崩溃恢复 |
| 有 redo 就不需要 binlog | redo 不适合主从复制和时间点恢复 |
| 提交成功等于数据页已经刷盘 | 提交成功通常代表日志可靠,数据页可以后刷 |
sync_binlog=1 没成本 | 每次提交刷盘可靠性高,但会增加 IO 成本 |
| 大事务只是执行慢 | 大事务还会放大 redo、binlog、锁等待、复制延迟和恢复成本 |
面试标准回答
redo log 和 binlog 有什么区别?
redo log 是 InnoDB 引擎层日志,主要用于崩溃恢复,记录数据页的物理修改,采用循环写。事务提交后,即使数据页还没刷盘,只要 redo log 可靠落盘,MySQL 重启后就能重放 redo 恢复已提交数据。binlog 是 MySQL Server 层日志,主要用于主从复制、备份后的增量恢复和审计,记录 SQL 事件或行变更事件,采用追加写。两者通过两阶段提交保证同一事务在 redo 和 binlog 中保持一致。
为什么需要两阶段提交?
因为 redo log 和 binlog 属于不同层。如果 redo 写成功但 binlog 没写成功,主库崩溃恢复后有这次变更,从库却无法复制;如果 binlog 写成功但 redo 没写成功,从库可能回放了变更,主库恢复后却没有。两阶段提交先让 redo 进入 prepare 状态,再写 binlog,最后提交 redo。崩溃恢复时,如果 redo 是 prepare,就检查 binlog 是否完整,完整则提交,不完整则回滚。
sync_binlog 和 innodb_flush_log_at_trx_commit 怎么设置?
核心交易系统通常设置 sync_binlog=1 和 innodb_flush_log_at_trx_commit=1,可靠性最高,表示每次事务提交都尽量把 binlog 和 redo 刷盘。代价是 fsync 成本更高,写入吞吐依赖磁盘能力。对可重放、可容忍少量丢失的数据,可以根据业务容忍度选择更偏性能的配置,但订单、支付、资产、库存类系统不建议随意降低可靠性。
小结
- redo log 属于 InnoDB,解决崩溃恢复和持久性。
- binlog 属于 Server 层,解决主从复制、增量恢复和审计。
- MySQL 通过两阶段提交让 redo 和 binlog 在事务边界保持一致。
- 提交成功通常不代表数据页已经刷盘,而是日志已经按策略可靠写入。
- undo log 负责回滚和 MVCC 旧版本,undo 页的修改也会受到 redo 保护。
- 生产中要重点关注大事务、复制延迟、刷盘参数、磁盘 fsync 和恢复链路。
关联学习:
