bigywater.tech
MySQL / InnoDB · 103

复制冲突

真实配一对循环复制,停掉复制线程后往同一主键各自写入不同数据,重启复制看真实的冲突会怎么爆发、又留下什么样的真实后果。

2个SQL线程同时崩
真实的Duplicate entry复制冲突
永久数据分叉
跳过冲突后真实没有任何机制自动纠正

做的事:真实配一对循环复制,制造一次真正的双写冲突

代码-- r1和r2互为彼此的source(循环复制) -- 真实先停掉两边的复制线程,让两次冲突的写入都能各自在本地成功 STOP REPLICA; (在r1和r2上都执行) INSERT INTO app.events (id, msg) VALUES (100, 'from-r1'); -- 在r1上 INSERT INTO app.events (id, msg) VALUES (100, 'from-r2'); -- 在r2上

验证:真实两边各自成功,数据已经真实分叉

实测 · 本机执行输出
r1: id=100 msg='from-r1'
r2: id=100 msg='from-r2'   ← 真实两边此刻各自都合法,只是数据不一样

因为复制线程真实提前停掉了,这两条INSERT在各自本地都真实顺利通过了主键约束检查(此时对方那条真实还没同步过来),两边真实各自拥有了同一个主键、不同内容的一行数据——这是循环复制里冲突真实发生的经典前提:写入发生得足够接近,复制没能及时把对方的变更同步过来做冲突检测。

验证:真实重启复制,两边SQL线程同时真实崩溃

代码START REPLICA;(两边同时)
实测 · 本机SHOW REPLICA STATUS真实输出
r1: Replica_SQL_Running: No
    Last_Error: Worker 1 failed executing transaction '...r2-bin...'
    (真实是应用r2那条INSERT时,撞上本地已经存在的id=100,Duplicate entry)

r2: Replica_SQL_Running: No
    Last_Error: Worker 1 failed executing transaction '...r1-bin...'
    (真实是应用r1那条INSERT时,同样撞上本地已存在的id=100)
两边真实同时崩溃,报的都是Duplicate entry——这正是循环复制/多主复制最真实的风险:没有任何内建机制去仲裁"这个主键冲突应该以谁为准",MySQL的默认行为是真实让复制直接停下来,把决定权交给人。本文用了跟前面几篇一样的老办法(跳过冲突事务的GTID空提交)真实让两边SQL线程重新跑起来,但真实的结果是:跳过之后,r1永久保留了它自己的'from-r1',r2永久保留了它自己的'from-r2',报错真实是消失了,但两边数据从此真实永久性地分叉,没有任何自动机制会去纠正它——这也是为什么真实的生产多活方案(Group Replication的多主模式、或者应用层的分片/冲突避免设计)都要求业务层面从设计上避免这种"同一个主键两边都可能写"的场景,而不是依赖复制层去事后调解。

分步演示:从双写到崩溃,再到永久分叉的真实过程

每一步都是本文上面用真实循环复制环境跑出来的原始结果。

报错消失了,但真实的分叉留了下来
这一步新验证的状态

    本文用到的工具

    • 本机真实搭建的循环复制实验环境(两节点互为source)
    • 真实制造的主键冲突 + GTID空事务跳过冲突事务的恢复手段
    京ICP备2026067502号-1京公网安备11010502063487号