binlogless 从库 + 并行复制:复制的 FLUSH PRIVILEGES 在 mysql.gtid_executed 里留空洞,重启后复制中断
信源与结论
官方 release note 结论:MySQL 9.7.4(2026-10-20)Replication Notes:
On a binlogless replica using relay-log recovery, parallel replication, and commit-order preservation, replicated
FLUSH PRIVILEGESstatements could leave gaps inmysql.gtid_executed. After restart, the replica could request already-applied transactions, resulting in duplicate errors or failure when the source had purged the required binary logs.
报错文本形如:
Cannot replicate because the source purged required binary logs.
Replicate the missing transactions from elsewhere, or provision a new replica from backup
Bug #115613, Bug #36849620。Release notes:
触发条件需要同时满足四项:从库 log_bin=OFF(binlogless)、使用 relay-log recovery、开启并行复制、开启 commit-order preservation。缺任何一项,都没落在该 bug 描述的场景里。
故障链拆解
推断(需自行验证),以下是参数语义层面的解释,不是 release note 原文:
gtid_executed记录已执行事务的 GTID 集合。当log_bin=OFF且log_replica_updates=OFF时,从库不把应用过的事务写进自己的 binlog,gtid_executed的维护就落到mysql.gtid_executed表上(默认每 1000 个事务刷一次,gtid_executed_compression_period控制压缩)。- 并行复制 + commit-order preservation(
replica_preserve_commit_order=ON,依赖log_replica_updates或 binlog)下,事务按主库顺序提交,但并行 worker 各自应用的先后与 GTID 集合更新并不是同一把锁。FLUSH PRIVILEGES这类管理语句走特殊处理路径,可能绕过常规的 GTID 写入路径,于是表里出现空洞。 - 空洞的后果:重启后从库按
gtid_executed与 relay log 当前位点比对,发现主库上「某些 GTID 自己没记为已执行」,于是重新请求这些事务。如果主库已经把对应 binlog 清掉(binlog_expire_logs_seconds到期),复制直接报错中止。 Cannot replicate because the source purged required binary logs是ER_SOURCE_FATAL_ERROR_READING_BINLOG/ 相关错误路径的典型提示。
识别:先看参数和日志
SELECT @@global.log_bin, @@global.log_replica_updates, @@global.gtid_mode;
SELECT @@global.replica_preserve_commit_order, @@global.replica_parallel_workers;
SELECT COUNT(*) FROM mysql.gtid_executed;
SELECT @@global.gtid_executed;
-- 比对主库与从库 GTID 集合差异(用 gtid_subset / 逐段比较)
-- 8.0+ 可用:
SELECT GTID_SUBTRACT(@@global.gtid_executed /*source*/, (SELECT ...)) -- 需按实际拼装
错误日志里搜 ER_MASTER_FATAL_ERROR_READING_BINLOG / Cannot replicate because the source purged。日志里同时会打出缺失的 GTID 段,可以直接和主库的 gtid_executed、mysql.gtid_executed 对齐看差在哪一段。
复现思路
推断(需自行验证):按上面四个条件搭一套环境——从库 log_bin=OFF、log_replica_updates=OFF、replica_parallel_workers>1、replica_preserve_commit_order=ON;主库执行 FLUSH PRIVILEGES 并让对应 GTID 应用到从库;然后在从库核对 mysql.gtid_executed 里是否落了这条 GTID。确认有空洞后重启从库 SQL 线程,并把主库 binlog 清到该 GTID 之前,即可复现报错。日常环境不必特意构造,巡检时直接查 GTID 集合差异更快。
规避与修复
- 升级到 9.7.4(含修复)。旧版本仍在跑的,升级窗口内不要把主库 binlog 保留期设得太短。
- 主库
binlog_expire_logs_seconds留出足够窗口:至少覆盖一次全量备份周期 + 从库最长滞后时间。 - 从库上开
log_replica_updates=ON并保留 binlog(磁盘允许的话),让gtid_executed走 binlog 路径,减少对mysql.gtid_executed表的依赖。 - 定期比对主从 GTID 集合,发现从库缺 GTID 立刻处理(重新拉取 / 重建),不要等到主库 purge 之后才发现。
- 已经报这个错之后的恢复:从备份重建从库通常比手工补事务更稳;也可以从其他从库 / 延迟从库上补,但必须先确认那边的 GTID 集合是完整的。
FLUSH PRIVILEGES这类语句常出现在业务代码里(不少框架的权限刷新逻辑会调用),它不是业务 DML,容易被漏掉,排查复制问题时别只盯着表和 DML。