人人都会AI编程

日志写入机制、刷盘策略、两阶段提交

更新时间:2026-07-10

在了解三大日志各自的作用之后,开发者最常有的困惑是:这些日志到底什么时候写?写到哪儿?怎么样才能又安全又快?这一节聚焦在实际写入过程的细节上,理清 Redo Log、Undo Log、Binlog 的写入与刷盘策略,以及保证它们一致性的两阶段提交机制。

日志写入机制

Redo Log 的写入

事务执行过程中对数据页的修改,不会立刻直接写入磁盘的数据文件。InnoDB 会先将修改记录到内存中的 Redo Log Buffer,这是一个环形缓冲区,大小由 innodb_log_buffer_size 控制(默认 16MB)。

Redo Log Buffer 中的内容会在以下时机写入磁盘上的 Redo Log 文件:

  • 每隔一秒,后台主线程会定期刷新。
  • 当 Redo Log Buffer 使用空间超过一半时。
  • 当事务提交时,根据 innodb_flush_log_at_trx_commit 参数的设置决定刷新行为(这一步最关键)。

Redo Log 文件是固定大小的,通常配置为一组两个或多个文件,写满后会循环覆盖。这也就是为什么 Redo Log 叫“重做日志”——它只记录修改动作,不保留历史本版,循环写、空间可控。

Undo Log 的写入

Undo Log 同样是事务修改前的反向记录,但它并不是独立写入一份日志文件,而是寄生在 Redo Log 和表空间中。更准确地说:

  • Undo Log 的修改本身也会产生 Redo Log,也就是说 Undo 的写入是受 Redo Log 保护的。
  • Undo Log 实际存储在共享表空间或单独的 Undo 表空间中(MySQL 8.0 默认使用独立的 Undo 表空间,便于管理和截断)。
  • 事务提交后,Undo Log 并不会立即删除,而是由 Purge 线程根据当前活跃事务的需要决定是否回收。这也是长事务会导致 Undo Log 膨胀的原因。

Binlog 的写入

Binlog 是 MySQL Server 层产生的逻辑日志,记录的是每条更改数据的 SQL 语句或行变更。每个线程在事务执行过程中,会先将产生的 Binlog 事件写入私有的 Binlog Cache(大小由 binlog_cache_size 控制)。

一旦事务提交,Binlog Cache 中的内容会被一次性写入磁盘上的 Binlog 文件;如果事务回滚,Binlog Cache 直接丢弃。大事务可能导致 Binlog Cache 写满后使用临时文件,因此配置合适的 buffer 大小可以避免磁盘临时文件的额外 I/O。

有一点很重要:Binlog 文件是追加写,不会像 Redo Log 那样循环覆盖。 这意味着 Binlog 会持续增长,需要配合备份和过期清理策略(expire_logs_daysbinlog_expire_logs_seconds)来管理磁盘空间。

刷盘策略:安全与性能的权衡

三个和日志刷盘相关的核心参数,直接决定性能与数据安全的平衡点,是生产环境调优绕不开的。

innodb_flush_log_at_trx_commit

控制 Redo Log 的刷盘行为(只影响 InnoDB 引擎):

  • 0:事务提交时不立刻写 Redo Log 到磁盘,而是每秒写入 OS 缓存并刷盘一次。性能最好,但如果 MySQL 或服务器意外断电,可能丢失最近 1 秒内的已提交事务数据。
  • 1:每次事务提交都将 Redo Log Buffer 的内容写入 OS 缓存,并调用 fsync() 确保持久化到磁盘。完全不会丢数据,但每次提交都要一次磁盘同步,写入性能会受影响。
  • 2:每次事务提交只把 Redo Log Buffer 写入 OS 缓存,但不主动调 fsync(),由操作系统每秒自动刷盘。MySQL 宕机不会丢数据,但服务器断电可能丢失最近 1 秒内的已提交数据。

生产环境建议:对数据一致性要求最高的场景(如金融、订单),必须设置为 1;对性能要求极高且允许极小数据丢失的(如日志收集、点击流),可以设置为 20 风险较高,线上一般不推荐。

sync_binlog

控制 Binlog 的刷盘频率(MySQL Server 层面):

  • 0:提交事务时只把 Binlog 写入 OS 缓存,不主动刷盘,由操作系统决定什么时候写入磁盘。操作系统崩溃可能丢失 Binlog 事件。
  • 1:每次事务提交都将 Binlog Cache 的内容同步刷写到磁盘。这是最安全的设置,与 innodb_flush_log_at_trx_commit=1 配合能保证事务完全持久化。
  • N(N>1):表示每 N 次事务提交才刷一次盘。这是折中方案,但主从复制场景下最好设置成 1,否则从库可能会因为主库断电而落后大于一个事务。

生产环境建议:使用主从复制或对数据零丢失有要求的系统,设置 1;单个实例且允许少量日志丢失的性能敏感场景,可以考虑 100 左右的批次刷盘。

组合效果:如果 innodb_flush_log_at_trx_commitsync_binlog 都是 1,每一个事务提交都要同步两次磁盘(Redo Log 和 Binlog 各一次),这就是所谓的“双 1 配置”,最安全但写性能相对较低。对于机械硬盘,会成为性能瓶颈;对于 SSD 且有 RAID 或云盘,通常可以承受。如果你的写入 QPS 特别高,可以考虑使用带缓存的 RAID 卡或云盘,它们自身有断电保护,能在保证安全的前提下缓解 IO 压力。

Undo Log 刷盘:Undo Log 的持久化通过 Redo Log 来保证,因此不需要单独设置刷盘策略。只要 Redo Log 被安全刷盘,Undo 修改也会被还原。

两阶段提交:Redo Log 与 Binlog 的一致性问题

如果只有一套日志,提交要么成功要么失败,不会有中间状态。但 MySQL 存在两套日志:InnoDB 的 Redo Log 和 Server 层的 Binlog,而事务的提交必须同时写入这两者。如果没有协调机制,崩溃可能造成两种不一致:

  • 先写 Redo Log 再写 Binlog:如果写完 Redo Log 后崩溃,Binlog 未写。重启后 Redo Log 会让该事务恢复(认为已提交),但 Binlog 中没有记录,导致从库少一个事务,主从数据不一致。
  • 先写 Binlog 再写 Redo Log:同理,如果写完 Binlog 后崩溃,Redo Log 中没有对应记录,重启后该事务丢失,但 Binlog 中却多了一个事务,从库比主库多数据,同样灾难。

为解决这个问题,MySQL 采用了内部 XA 分布式事务的思想,将 Redo Log 和 Binlog 的写入过程变成两阶段提交:

  1. Prepare 阶段:InnoDB 将 Redo Log 写入并刷盘,标记为 prepare 状态。此时事务还未最终提交。
  2. Commit 阶段:Server 层写入 Binlog 并刷盘,之后 InnoDB 再将 Redo Log 中的对应记录标记为 commit 状态,事务提交完成。

如果在任何一步发生崩溃,恢复流程会检查 Binlog 和 Redo Log 的状态:

  • 如果 Binlog 未完整写入(未刷盘),则在 prepare 状态的 Redo Log 记录会被回滚。事务丢失,但主从一致。
  • 如果 Binlog 已完整写入,即使 Redo Log 还处于 prepare 状态,恢复时也会根据 Binlog 中的事务记录,自动将 InnoDB 中的 prepare 事务提交(commit),保证该事务在存储引擎中也成功且主从一致。

这套机制确保了 Redo Log 和 Binlog 在崩溃之后总是一致的,是 MySQL 复制和高可用方案有效性的基石。

对于开发者来说,你通常不需要操心两阶段提交的细节。但理解它能帮你解释一些现象:比如为什么 sync_binlog=1innodb_flush_log_at_trx_commit=1 同时开启后,每个事务提交需要两次刷盘(一次 Redo Log prepare 刷盘,一次 Binlog 刷盘),以及为什么这样能保证主从数据绝对一致。

在实际运维中,双 1 配置加上半同步复制是金融级高可靠的标配。即便有牺牲写入性能的地方,也可以考虑使用组提交(Group Commit)来批量刷盘,MySQL 内部会合并多个事务的刷盘请求,减少实际 fsync() 调用次数,从而提升吞吐量。8.0 版本对组提交做了进一步优化,在高并发写入场景下,双 1 配置的性能已经比 5.7 时代好不少。