土法炼钢 · 系统与基础设施

【MySQL InnoDB 内核】Binlog 与两阶段提交:XA、组提交与持久性语义

文章导航

分类入口
databasekernel
标签入口
#mysql#innodb#binlog#2pc#xa#group-commit#ordered-commit#trx0trx

目录

Binlog 与两阶段提交:XA、组提交与持久性语义

主库 sync_binlog=1innodb_flush_log_at_trx_commit=1 时,一次 COMMIT 可能触发 redo fsync + binlog fsync 两次磁盘同步——TPS 上限常被 log IO 钉死。若把 innodb_flush_log_at_trx_commit 改成 2 想「换性能」,崩溃窗口却可能丢最后一秒事务。根因是 MySQL 同时维护 InnoDB redo(引擎崩溃恢复)与 binlog(复制与 PITR)两条日志,必须用 内部 XA 两阶段提交 对齐顺序。

本文以 MySQL 8.0.36 为主线,追踪 ha_commit_transordered_committrx_commit_for_mysql 路径,对照 Redo Log 篇 的 LSN 语义与 第 13 篇 的复制延迟来源。


一、为什么需要两条日志

InnoDB redo 记录 页级物理变更,崩溃后由 recv_recovery_from_checkpoint_start 重放(第 10 篇)。binlog 记录 逻辑事件WRITE_ROWSUPDATE_ROWS 等),供主从复制与 mysqlbinlog PITR。

若只写 redo 不写 binlog:引擎可恢复,从库拿不到变更。若只写 binlog 不写 redo:复制能继续,单库崩溃可能丢已提交事务的页级修改。因此 DML 在 COMMIT 时必须 两条日志一致落盘——这是内部 XA 的动机。

flowchart LR
  DML[UPDATE/INSERT] --> UNDO[undo log]
  DML --> REDO[redo log buffer]
  DML --> BIN[binlog cache]
  COMMIT[COMMIT] --> XA[XA 2PC]
  XA --> REDOFS[redo flush]
  XA --> BINFS[binlog flush]
日志 所属层 崩溃恢复 复制
redo InnoDB 否(物理)
binlog Server 配合 InnoDB 是(逻辑)

二、XA 事务与 XID

MySQL 使用 内部 XA(非外部 TM):XA START / XA END / XA PREPARE / XA COMMIT 的存储引擎接口由 handlerton 实现。每个事务分配 XIDformatID + gtrid + bqual),写入 binlog 与 InnoDB undo。

源码(8.0.36):

崩溃恢复时,Server 扫描 binlog 中 XA PREPARE 状态的 XID,询问 InnoDB 提交或回滚——与 第 10 篇 redo 阶段衔接。


三、提交流程:prepare → engine commit → binlog commit

典型 COMMITbinlog_order_commit=ON,8.0 默认)顺序:

ha_commit_trans()
  → tc_log->commit()                    // TC_LOG_BINLOG
    → MYSQL_BIN_LOG::ordered_commit()
        阶段 1: flush to binlog cache(各线程写事件)
        阶段 2: sync_binlog(若达到 sync 条件)
        阶段 3: ha_commit_low()         // 调 handlerton::commit
            → trx_commit_for_mysql()    // InnoDB redo flush(受 innodb_flush_log_at_trx_commit 约束)
        阶段 4: binlog commit 标记完成
sequenceDiagram
  participant THD as 连接线程
  participant BIN as MYSQL_BIN_LOG
  participant INN as InnoDB trx
  participant REDO as redo log
  THD->>BIN: 写事务事件到 binlog cache
  THD->>BIN: ordered_commit flush stage
  BIN->>BIN: sync_binlog(可选 fsync)
  THD->>INN: ha_commit_low / trx_commit
  INN->>REDO: log_write_up_to(可选 fsync)
  THD->>BIN: 标记 binlog 事务完成

组提交(group commit):多线程在 ordered_commit 的 flush/sync 阶段排队,一次 fsync 刷多个事务的 binlog,摊薄磁盘延迟。高并发下 log IO 吞吐常由组提交效率决定,而非单事务 redo 大小。


四、持久性参数组合

innodb_flush_log_at_trx_commit 行为
0 每秒后台刷 redo,commit 不主动 flush
1 每次 commit log_write_up_to 到磁盘(默认)
2 commit 写 OS cache,每秒 fsync
sync_binlog 行为
0 由 OS 刷 binlog,不主动 fsync
1 每次 commit 尝试 fsync binlog(默认)
N>1 每 N 次 commit 或定时 fsync

最强持久性1 + 1——主库崩溃不丢已提交事务(在单实例 fsync 语义下)。性能优先2 + 02 + 大 sync_binlog——明确接受崩溃丢最近事务窗口。

工程判断:金融核心库保持 1+1;只读从库或延迟容忍场景可放宽从库参数,主库放宽前必须评估 RPO。


五、源码锚点(MySQL 8.0.36)

组件 路径 关键符号
事务协调 sql/handler.cc ha_commit_trans, ha_commit_low
TC 日志 sql/tc_log.cc tc_log_binlog
binlog sql/binlog.cc MYSQL_BIN_LOG::ordered_commit, sync_binlog_period
InnoDB 提交 storage/innobase/trx/trx0trx.cc trx_commit_for_mysql, trx_prepare_for_mysql
redo 刷盘 storage/innobase/log/log0log.cc log_write_up_to, log_buffer_flush_to_disk

binlog_group_commit_sync_delay / binlog_group_commit_sync_no_delay_count(8.0)可人为 微延迟 以攒更多事务进同一 fsync 批次——吞吐与 p99 延迟的显式权衡。


六、与 redo LSN 的关系

InnoDB 提交后 trx->commit_lsn 推进;checkpoint 与 Buffer Pool flush 列表 仍按 LSN 回收 redo。binlog positionmysql.gtid_executed / file+pos)是复制游标——两者 无简单数值对应,PITR 需同时有 全量备份 LSN 边界binlog 位点第 19 篇)。


七、实验(需本地验证)

7.1 观察组提交批次

-- 开启 performance_schema(需重启或动态开启相关 consumer)
-- SHOW VARIABLES LIKE 'performance_schema';

-- 高并发短事务:sysbench oltp_update_index
-- 同时采样:
-- SELECT EVENT_NAME, COUNT_STAR FROM performance_schema.events_waits_summary_global_by_event_name
--   WHERE EVENT_NAME LIKE '%binlog%';

7.2 参数组合与崩溃语义(仅测试环境)

SET GLOBAL innodb_flush_log_at_trx_commit = 2;
SET GLOBAL sync_binlog = 0;
-- 压测写入后 kill -9 mysqld,启动后检查最后一笔事务是否可见
-- 恢复为 1/1 后再用于生产

7.3 binlog 与 XID

-- 测试库开启 binlog_row_image=FULL
BEGIN;
UPDATE sbtest.sbtest1 SET k=k+1 WHERE id=1;
COMMIT;
-- mysqlbinlog --verbose 查看 Xid_log_event 与 ROWS 事件顺序(本地执行)

八、工程坑点

只调 innodb_flush_log_at_trx_commit 忽略 sync_binlog。主库仍可能在 binlog 未落盘时报告提交成功(sync_binlog=0),主从切换丢数据。

误以为从库参数可放宽主库 RPO。RPO 由 主库 持久性参数决定;从库异步复制额外增加延迟窗口。

组提交延迟配过大binlog_group_commit_sync_delay 毫秒级延迟在高 QPS 下抬高 p99,却未必线性提升吞吐。

把 Aurora/RDS 内部 redo 路径等同于社区版。托管服务可能分离存储与日志——本文以社区版为准。


九、边界与版本差异


十、关键要点

  1. redo + binlog 双日志要求内部 XA;ordered_commit 实现组提交摊薄 fsync。
  2. innodb_flush_log_at_trx_commit + sync_binlog 共同定义单主 RPO,不是独立旋钮。
  3. InnoDB commit 在 binlog sync 之后由 ha_commit_low 触发——顺序与复制一致性绑定。
  4. XID 是崩溃后跨层协调的纽带。
  5. 性能调优应先看 log IO 饱和度 与组提交批次大小,再动持久性参数。

十一、PG 对照

维度 InnoDB + binlog PostgreSQL
持久化日志 redo(引擎)+ binlog(Server) 单一 WAL
提交原子性 内部 XA 2PC 单次 WAL insert + flush
复制载体 binlog(逻辑) WAL 物理流(第 18 篇
组提交 ordered_commit 批 fsync XLogFlush 批处理

PG 不存在「引擎提交成功但 binlog 未写」的分叉;MySQL 的 2PC 是为 逻辑复制 付出的工程税。从 PG 转 MySQL 时,应把 sync_binlog/innodb_flush_log_at_trx_commit 当作 WAL 持久性旋钮的翻倍版


上一篇B+Tree 与索引

下一篇主从复制机制

参考资料

源码(MySQL 8.0.36)

官方文档

相关文章

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-06-18 · database / kernel

【MySQL InnoDB 内核】InnoDB 存储引擎机制深度拆解

从线程模型到页格式、从 undo log MVCC 到 binlog 两阶段提交——对 MySQL InnoDB 做源码级拆解,并与 PostgreSQL 内核系列逐章对照。20 篇覆盖内核机制与生产运维实战,面向 MySQL DBA、从 PG 转 MySQL 的后端与数据库内核开发者。


By .