MySQL双主多活防环防冲突的核心是:必须开启log_slave_updates且server-id全局唯一,结合GTID去重、复制过滤规则(如replicate_ignore_db)、中间件打标及写入域隔离,否则必然导致循环复制与数据覆盖。

多IDC部署下做MySQL多活,数据互备本身不难,难的是防环和冲突——binlog双向流转时,一条UPDATE从A机房发到B,又被B原样复制回A,就会无限循环;更糟的是,两个机房同时写同一张表的同一行,最终数据会覆盖错乱。必须用机制掐断环路、隔离写入域、明确冲突裁决规则。
为什么不能直接开双主复制(MASTER_AUTO_POSITION=1 + log_slave_updates)
看似简单:A→B 和 B→A 同时开启复制,log_slave_updates 打开让从库也记 binlog,就能双向同步。但实际会立刻出问题:
- 环形复制:A写入事务T,生成
binlog发给B;B执行后,因log_slave_updates=ON又生成新binlog,再发回A;A再次执行T,形成死循环 - 自增主键冲突:
auto_increment_offset和auto_increment_increment必须配对设置,否则两边INSERT可能撞ID - 无冲突检测:MySQL本身不判断“这条UPDATE是不是我刚自己写的”,全靠应用层或中间件识别来源
用replicate_ignore_db和replicate_do_table做写入域隔离
多活不是所有库/表都双写,而是按业务维度拆分写入归属。比如订单库只在IDC-A写,用户库只在IDC-B写,其他只读库可全量同步。这时要用复制过滤规则把“非本IDC写入源”的变更拦掉:
- 在IDC-A的MySQL配置中加:
replicate_ignore_db = user_db,确保来自IDC-B的user_db变更不被执行 - 在IDC-B的MySQL配置中加:
replicate_do_table = order_db.orders,只同步order_db下的orders表(如果该表确需跨IDC读) - 注意:
replicate_wild_ignore_table比replicate_ignore_db更精准,例如replicate_wild_ignore_table = %.tmp_%可忽略所有临时表
用GTID + gtid_executed + 中间件打标实现防环
纯靠MySQL参数无法彻底防环,必须配合中间件(如ShardingSphere、Vitess)或应用层在事务开始前注入唯一写入标记。关键点是:
- 每个IDC的写请求必须带
X-DC: shanghai这类标识,中间件将其转为注释写入SQL,例如/* X-DC: shanghai */ UPDATE t SET x=1 WHERE id=1 - MySQL启用
enforce_gtid_consistency=ON,所有事务生成GTID;各IDC维护自己的gtid_executed集合 - 复制通道上部署轻量过滤器(如基于
mysqlbinlog解析+正则匹配),遇到含X-DC: beijing的语句且当前实例是北京IDC,则跳过执行——这一步必须在SQL线程之前拦截,不能等进入relay log再判
冲突解决不能依赖MySQL自动机制
MySQL没有内置的“最终一致性”冲突合并能力。当两个IDC几乎同时更新同一行时,必须提前约定策略:
- 时间戳决胜:要求所有写入带
updated_at字段,冲突时取MAX(updated_at),但要注意NTP时钟漂移 - 版本号决胜:用
version字段做CAS更新,失败则由应用重试或降级为补偿任务 - 写入域兜底:最稳妥的方式是禁止跨IDC写同一行——通过分库分表路由规则强制,比如
user_id % 2 == 0 → IDC-A,user_id % 2 == 1 → IDC-B,连冲突机会都不给
真正容易被忽略的是:binlog_format=ROW下,即使你用了replicate_ignore_db,只要事务里混了多个库的操作,整条binlog event仍会被拒收——所以写入必须严格按库隔离,不能在一个事务里跨库UPDATE。


















