返回首页

20|binlog 与两阶段提交

适用版本:MySQL 8.0.x

只要开始接触 MySQL 架构,binlog 几乎一定会出现。因为它不只是“一个日志文件”,而是很多关键能力的基础:

  • 主从复制依赖它
  • Point-in-Time Recovery 依赖它
  • 数据审计与变更追踪常常离不开它
  • 事务提交一致性,也和它有紧密关系

而真正让很多同学卡住的地方,不是“binlog 是什么”,而是:

  • 为什么已经有 redo log 了,还要 binlog?
  • binlog 和 redo log 的边界到底在哪?
  • 为什么事务提交时要用两阶段提交?
  • 如果数据库在某个时间点崩溃,到底怎么保证 binlog 和引擎数据一致?

这篇文章就围绕这些核心问题展开。


一、先搞清楚:binlog 是谁的日志

1. binlog 属于 Server 层

MySQL 是分层架构:

  • Server 层:连接器、解析器、优化器、执行器、binlog 等
  • 存储引擎层:InnoDB、MyISAM 等

binlog 是 MySQL Server 层 的日志,不属于 InnoDB 专属能力。

这意味着:

  • 理论上不止 InnoDB 会用到 binlog
  • binlog 更偏向 MySQL 层面对“事务变更事件”的统一记录

2. redo log 属于 InnoDB 引擎层

redo log 是 InnoDB 内部的物理日志,用来保证崩溃恢复。

所以两者的分工可以先简化理解为:

  • redo log:保障 InnoDB 已提交事务的持久性和 crash-safe
  • binlog:记录逻辑变更,用于复制、恢复、审计等 MySQL 层能力

这就是“为什么已经有 redo 还需要 binlog”的第一层答案:

它们服务的目标不同,所在层次也不同。


二、binlog 到底记录什么

1. binlog 记录的是逻辑变更事件

和 redo log 的页级物理修改不同,binlog 更偏向逻辑层的事务变更信息。

MySQL 8.0 中常见的 binlog 格式有三种:

  • STATEMENT:记录原始 SQL
  • ROW:记录每行数据怎么变
  • MIXED:由 MySQL 自动选择 statement 或 row

工程实践里,最常用的是 ROW 模式。

2. 为什么生产环境普遍偏向 ROW

因为它更准确,更适合复制和恢复。

比如下面这类 SQL:

UPDATE account SET amount = amount + RAND() WHERE id < 10;

如果只记录 SQL 语句,在从库重放时可能得到不同结果;而 ROW 记录的是每一行具体变更前后值,结果更确定。

当然,ROW 模式的 binlog 体积通常更大,但在正确性和可预期性面前,这个代价通常是值得的。


三、binlog 的典型用途

1. 主从复制

主库把事务写入 binlog,从库读取并重放这些事件,从而实现数据同步。

2. 基于时间点恢复(PITR)

如果你有一份全量备份,再配合某个时间段的 binlog,就可以把数据恢复到误操作前的某个时间点。

3. 审计与变更追踪

在很多系统中,binlog 也是数据变更订阅、审计分析、CDC 链路的基础输入。

所以 binlog 的意义远不止“给从库用”。


四、仅有 binlog 或仅有 redo,各自有什么问题

这是理解两阶段提交前最关键的铺垫。

1. 只有 redo,不够

如果只有 redo:

  • InnoDB 自己可以做崩溃恢复
  • 但 MySQL Server 层拿不到统一的逻辑变更流水
  • 主从复制、PITR、CDC 会失去基础

2. 只有 binlog,也不够

如果只有 binlog:

  • 可以知道“事务逻辑上提交了什么”
  • 但无法替代 InnoDB 的页级崩溃恢复机制
  • 事务提交时,也无法像 redo 那样高效保障引擎内部持久性

所以在 InnoDB + MySQL 的组合里:

  • redo 负责引擎内的 durability
  • binlog 负责 MySQL 层的逻辑事件输出

两者必须协同。


五、核心问题:为什么需要两阶段提交

如果一个事务既要写 redo,又要写 binlog,那自然会出现一个问题:

先写哪个?如果中间崩溃怎么办?

这正是两阶段提交要解决的事。

1. 如果先写 redo,再写 binlog

假设流程如下:

  1. 事务更新成功
  2. redo 已经提交成功
  3. binlog 还没写入
  4. 数据库崩溃

重启后,InnoDB 会根据 redo 恢复出这笔已提交事务。

问题是:

  • 主库数据已经有了
  • 但 binlog 没有这条事务
  • 从库复制不到

于是主从数据不一致。

2. 如果先写 binlog,再写 redo

再看另一种情况:

  1. binlog 已经写成功
  2. redo 还没提交成功
  3. 数据库崩溃

此时可能出现:

  • binlog 中存在这笔事务
  • 但引擎数据并没有真正完成提交或恢复
  • 从库会执行这笔事务
  • 主库重启后却没有这笔数据

同样会出现主从不一致。

3. 所以必须让二者“要么都成功,要么都失败”

这就是两阶段提交的本质目标:

保证 redo 与 binlog 在事务提交语义上保持一致。


六、MySQL 中两阶段提交的大致流程

这里说的是 InnoDB 与 binlog 协同提交事务时的经典流程。

假设执行:

BEGIN;
UPDATE orders SET status = 2 WHERE id = 1001;
COMMIT;

阶段 1:prepare redo

事务执行过程中:

  • 修改 Buffer Pool 中的数据页
  • 生成 undo
  • 生成 redo

到提交时,InnoDB 先把事务的 redo 写到“prepare 状态”。

可以理解为:

  • 引擎先声明“这笔事务我准备提交了”
  • 但此时还不能算最终完成提交

阶段 2:写 binlog

Server 层把该事务对应的 binlog 事件写入 binlog 文件,并按配置决定是否刷盘。

阶段 3:commit redo

当 binlog 写成功后,InnoDB 再把 redo 标记为 commit

到这里,这笔事务才算真正完成。

所以顺序可以概括成:

  1. redo prepare
  2. write binlog
  3. redo commit

七、崩溃恢复时如何借助两阶段提交保证一致性

两阶段提交最精彩的地方,在于它把“中途崩溃”的情况也纳入了可恢复逻辑。

1. 场景一:崩溃发生在 redo prepare 之前

这说明事务还没进入可提交状态。

结果:

  • redo 不完整
  • binlog 也没有
  • 事务视为失败

2. 场景二:崩溃发生在 redo prepare 之后、binlog 写入之前

这时重启恢复时会发现:

  • 有 prepare 状态的 redo
  • 但没有对应 binlog

系统会把这笔事务判定为未完成提交,最终回滚或丢弃其提交结果。

3. 场景三:崩溃发生在 binlog 写入之后、redo commit 之前

这是最关键的一种情况。

重启后,MySQL 会检查:

  • redo 里存在 prepare 记录
  • binlog 中也存在对应事务完整事件

那么系统会认为:

  • 这笔事务其实已经应该提交
  • 于是依据 binlog / 提交标志完成恢复

4. 场景四:崩溃发生在 redo commit 之后

这时二者都已经成功,直接按已提交事务处理即可。

这套机制保证了主库恢复后的状态和 binlog 的事务边界保持一致。


八、怎么理解“事务提交成功”

从应用层看,收到 COMMIT 成功返回,就意味着事务提交了。

但从数据库内部看,这个“提交成功”背后实际包含多个动作:

  • 引擎修改内存页
  • 生成 undo/redo
  • redo prepare
  • 写 binlog
  • redo commit
  • 按刷盘策略把日志真正持久化

所以事务提交从来不是一个单点动作,而是一条完整链路。

理解这点后,再排查“提交抖动”“复制不一致”“崩溃恢复异常”时,思路会清晰很多。


九、binlog 的刷盘参数同样重要

既然事务提交涉及 binlog,就不能只盯着 redo 参数。

1. sync_binlog

这是 binlog 的关键参数之一。

常见取值:

  • 1:每次提交都将 binlog fsync 到磁盘
  • N:每 N 次提交刷一次盘
  • 0:由操作系统控制刷盘时机

2. 与 innodb_flush_log_at_trx_commit 配合看

如果你希望事务提交尽可能可靠,通常要一起看:

  • innodb_flush_log_at_trx_commit
  • sync_binlog

常见高可靠组合:

  • innodb_flush_log_at_trx_commit = 1
  • sync_binlog = 1

这会带来更高的 fsync 成本,但能最大限度降低断电或系统崩溃时的丢失风险。

3. 工程上的平衡

如果是核心交易类系统,优先级通常是:

  1. 数据可靠性
  2. 一致性
  3. 吞吐

如果是日志、埋点、可容忍少量丢失的场景,才可能评估放宽刷盘频率。

但调整前一定要明确业务损失边界,而不是凭感觉改参数。


十、group commit:为什么高并发提交不会慢得离谱

如果每个事务都严格串行做:

  • redo prepare
  • 写 binlog
  • redo commit
  • 两边各自 fsync

那高并发下提交性能会非常差。

MySQL 为了提升吞吐,引入了 group commit(组提交) 思路。

1. 组提交的直观理解

多个并发事务会尽量“搭车”一起完成某些刷盘动作,减少 fsync 次数。

收益主要体现在:

  • 提高提交吞吐
  • 降低每个事务平均刷盘成本
  • 减少高并发下的日志 I/O 放大

2. 它和两阶段提交不冲突

两阶段提交解决的是 一致性问题

组提交解决的是 性能问题

前者关注“对不对”,后者关注“快不快”。

在真实生产环境里,这两套机制是协同工作的。


十一、binlog 格式在工程中的选择建议

1. ROW:最常见、最稳妥

适用于:

  • 主从复制
  • 数据订阅
  • 需要高一致性的线上系统

优点:

  • 结果确定
  • 避免大量 statement 模式下的不一致问题

代价:

  • 日志量更大

2. STATEMENT:体积小,但限制多

某些语句依赖上下文、函数、副作用,重放结果可能不稳定。

线上核心业务已较少优先选择它。

3. MIXED:折中方案

由 MySQL 自动判断,但工程上为了行为稳定可预期,很多团队还是直接统一用 ROW


十二、排障时最常见的几个问题

1. 为什么主从延迟很高

虽然延迟未必都由 binlog 导致,但可以优先检查:

  • 主库事务是否太大
  • binlog 生成量是否激增
  • 从库 SQL 线程是否重放不过来
  • 是否存在大事务导致单事务应用时间过长

2. 为什么一次批量更新把复制打崩了

因为大事务会同时放大:

  • redo 压力
  • binlog 体积
  • 从库重放耗时
  • 锁持有时间
  • 回滚成本

所以批量更新应尽量拆批。

3. 为什么明明 SQL 不慢,提交却慢

可能不是执行阶段慢,而是提交阶段卡在:

  • redo fsync
  • binlog fsync
  • group commit 等待
  • 磁盘延迟抖动

这类问题如果只盯执行计划,往往看不出来。

4. 为什么恢复后数据和复制状态看起来不一致

要重点检查:

  • 崩溃时事务处于哪一个提交阶段
  • binlog 是否完整
  • 参数配置是否导致最近事务可能未持久化
  • 是否有复制位点或 GTID 处理问题

十三、几个实用命令

1. 查看 binlog 相关参数

SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
SHOW VARIABLES LIKE 'sync_binlog';

2. 查看当前 binlog 文件与位点

SHOW MASTER STATUS;

在 MySQL 8.0 中,虽然更推荐使用 source/replica 术语,但很多命令历史上仍保留原命名。

3. 查看 binlog 内容

mysqlbinlog binlog.000123

常用于:

  • 排查误操作时间点
  • 查看事务是否写入 binlog
  • 分析复制事件内容

十四、一个面向工程实践的结论框架

当你在生产环境里观察事务提交链路时,可以按下面的顺序理解:

  1. SQL 执行阶段:修改 Buffer Pool,生成 undo/redo
  2. 提交协调阶段:redo 进入 prepare
  3. Server 层落日志:写 binlog
  4. 最终提交阶段:redo commit
  5. 持久化策略落地:redo 与 binlog 按各自刷盘参数处理
  6. 后续复制消费:从库根据 binlog 进行同步

这个框架能把“事务提交”和“主从复制”连起来看,而不是把它们拆成两个孤立话题。


十五、小结

binlog 与 redo log 都是事务提交过程中的关键日志,但它们不是重复建设:

  • redo log 解决引擎层崩溃恢复与持久性问题
  • binlog 解决 Server 层变更记录、复制与时间点恢复问题

两阶段提交 的意义在于:

把这两套日志体系在事务提交语义上绑定起来,避免“主库数据提交了但 binlog 没有”或“binlog 有了但主库没提交成功”这种不一致。

如果你已经理解了这篇文章,后续继续学习主从复制、GTID、半同步复制、故障切换时,就会更容易把整体链路串起来。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

19|redo 与 undo 日志

下一篇

21|主从复制原理