跨版本双主升级必然触发循环复制,因server_id重复、log_slave_updates关闭或GTID状态不一致任一缺失,均导致A→B→A日志闭环激活;升级中binlog解析、GTID校验及server_id加载时机变化,叠加配置未固化或顺序错误,使防循环机制全面失效。

跨版本双主升级时,循环复制不是“可能出问题”,而是只要配置没对齐,必然发生——server_id 重复、log_slave_updates 关闭、GTID 状态不一致,三者任一缺失都会让 A→B→A 的日志闭环重新激活。
为什么跨版本升级特别容易触发循环复制
跨版本(如 5.7 → 8.0)升级过程中,MySQL 对 binlog 解析、GTID 校验、甚至 server_id 加载时机都可能变化。旧版本从库在重启后若未正确读取配置文件中的 server-id 值,仍沿用内存中残留的旧值(比如 1),而新版本主库也设为 1,I/O Thread 就会静默丢弃所有来自对方的日志——不报错、不告警、SQL_Thread 看似运行,实则事务持续丢失。
更隐蔽的是 log_slave_updates:MySQL 8.0.22+ 支持动态设置,但 I/O Thread 初始化时只认配置文件值;若升级后仅执行 SET GLOBAL log_slave_updates = ON 而未写入 my.cnf,重启后立即失效,双向同步链路直接断裂。
- 5.7 升级到 8.0 后,
mysql.user表结构变更,若未执行mysqld --upgrade,权限系统异常可能导致复制账号认证失败,I/O Thread 反复重连,日志解析逻辑错乱 - GTID 模式下,
gtid_executed集合若因版本差异无法正确序列化(如 5.7 的 GTID 格式被 8.0 误判为非法),SQL Thread 会跳过过滤逻辑,把本该丢弃的同源事件重放一遍 - 某些云平台克隆实例默认继承原
server-id,升级前未人工修改,两节点实际已是相同 ID
必须在升级前固化且验证的三项配置
这三项不是“建议做”,而是任意一项未达标,升级后循环复制风险就不可控。
-
server-id必须唯一且写死在my.cnf中:执行SELECT @@server_id;结果必须与配置文件中server-id = N完全一致;升级后立刻查,不能依赖 SET GLOBAL 临时值 -
log_slave_updates = ON必须写入配置文件并重启生效:检查SHOW VARIABLES LIKE 'log_slave_updates';返回ON,且mysqld进程启动日志里有log_slave_updates is enabled类似提示 - GTID 状态必须提前对齐:所有节点执行
SELECT @@GLOBAL.gtid_mode, @@GLOBAL.enforce_gtid_consistency;,确保均为ON;升级前导出SELECT @@GLOBAL.gtid_executed;并比对,值必须完全相同(空集合也要一致)
升级顺序错误会直接绕过所有防循环机制
双主架构下,必须严格遵循「先升从库角色节点、再升主库角色节点」,且每台节点升级后要确认复制状态稳定,否则防循环配置形同虚设。
- 如果先升级 A(当前主库)到 8.0,而 B(当前从库)仍是 5.7,B 无法解析 A 新生成的 binlog event 类型(如 8.0 的
ANONYMOUS_GTID_LOG_EVENT),I/O Thread 报错中断,relay log 停滞;此时若手动跳过错误继续 START SLAVE,B 会把未完整解析的事件写入自己的 binlog,再发回 A,触发无序重放和循环 - GTID 模式下,若 A 升级后生成了新
server_uuid(如重建数据目录),其gtid_executed集合与 B 不再可比,B 会将 A 的全部事务视为“未执行过”而重复应用 - 每台节点升级后,必须等
Seconds_Behind_Master = 0且Retrieved_Gtid_Set = Executed_Gtid_Set才能进行下一台,不能只看Slave_IO_Running = Yes
最容易被忽略的 post-upgrade 验证点
升级完成不等于安全,真正决定是否发生循环复制的,是升级后第一轮完整事务流能否被正确识别和过滤。
在 A 和 B 上分别执行以下命令,结果必须严格匹配:
SELECT @@server_id, @@global.log_slave_updates, @@global.gtid_mode;
然后模拟一次写入:
- 在 A 执行
INSERT INTO test.t1 VALUES (1, 'a'); - 立刻在 B 上查
SHOW MASTER STATUS;,确认新事件已记入 binlog 且Binlog_Do_DB为空(避免过滤干扰) - 再在 A 上执行
SHOW BINLOG EVENTS IN 'mysql-bin.000001' FROM <pos> LIMIT 1;,检查该事件的Server_id字段是否为 B 的server-id(而非 0 或 1) - 最后在 A 的
SHOW SLAVE STATUS\G中确认Received_gtid_set已包含该事务,且Executed_gtid_set未重复增加——这才是 server-id 过滤生效的铁证
任何一环出现偏差,说明防循环链路已断,必须回退排查配置固化与 GTID 状态,而不是等待业务出问题才响应。


















