事务不一致的根本原因是MySQL异步复制机制下主库不等待从库执行完成即返回成功,叠加从库SQL线程单线程或有限并行导致回放乱序、延迟或跳过。

主从复制中为什么会出现事务不一致?
根本原因在于 MySQL 默认的异步复制机制:主库提交事务后不等待从库执行完成就返回成功,而从库 SQL 线程是单线程(5.6 及以前)或有限并行(5.7+ 基于 slave_parallel_workers),导致事务在从库回放时可能被乱序、延迟甚至跳过。
常见现象包括:
- 主库已更新某条记录,从库查不到或仍是旧值
- 主库执行了
INSERT+UPDATE两条语句,从库上UPDATE先于INSERT执行,报ERROR 1032 (HY000): Can't find record in table - 主库事务 A 和 B 并发写入不同表,从库因并行回放策略误判依赖关系,造成数据错乱
如何启用基于 WRITESET 的并行复制(MySQL 8.0+)
这是目前最实用的并发控制方案,它通过记录事务修改的行级哈希(WRITESET)来判断是否可安全并行——只要两个事务没修改同一行,就允许并发回放。
需在从库配置以下参数:
CHANGE REPLICATION SOURCE TO SOURCE_DELAY = 0, SOURCE_AUTO_POSITION = 1; START REPLICA;
并确保主从都启用:
-
binlog_transaction_dependency_tracking = WRITESET(主库) -
replica_parallel_type = LOGICAL_CLOCK(从库,MySQL 8.0.22+ 推荐设为WRITESET) -
replica_parallel_workers = 4(建议设为 CPU 核数的 1–2 倍,但不超过 16) -
replica_preserve_commit_order = ON(关键!保证事务提交顺序与主库一致)
注意:WRITESET 依赖 binlog_format = ROW,且对未显式指定主键/唯一键的表无效(会退化为传统模式)。
READ COMMITTED 隔离级别下主从延迟更隐蔽
当主库使用 READ COMMITTED 时,事务内多次 SELECT 可能读到不同快照;而从库即使延迟几秒,其 SQL 线程仍按 binlog 顺序执行。这会导致一种“逻辑一致但结果不一致”的假象:
BEGIN; UPDATE t SET v = v + 1 WHERE id = 1; SELECT v FROM t WHERE id = 1; -- 返回 101 UPDATE t SET v = v * 2 WHERE id = 1; SELECT v FROM t WHERE id = 1; -- 返回 202 COMMIT;
若从库延迟,在第二次 SELECT 执行前,另一并发事务已在主库把 v 改成 300,那么从库最终值是 202 而非 300 —— 表面看语句都执行了,实际业务语义已破坏。
这类问题无法靠复制参数修复,必须依赖应用层显式加锁(如 SELECT ... FOR UPDATE)或改用 REPEATABLE READ 并接受 MVCC 副作用。
GTID + WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS 实现强一致性读
当业务需要确认某次写入已同步到从库(例如刚下单就查订单),不能只靠 SELECT,得主动等待复制追平:
SELECT WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100');但要注意三点:
- 该函数阻塞当前连接,超时由
slave_checkpoint_group和slave_checkpoint_period控制,默认 300 秒 - 必须在从库执行,且要求
gtid_mode = ON、enforce_gtid_consistency = ON - 返回值为 -1 表示超时或 GTID 不存在,不是成功信号
生产中更稳妥的做法是:主库写入后,记录返回的 GTID(如 SELECT @@gtid_executed),再在从库轮询 SELECT RECEIVED_TRANSACTION_SET FROM performance_schema.replication_connection_status 和 SELECT GTID_SUBSET(...) 判断是否已接收并执行。
真正难处理的永远不是参数开关,而是那些没走主键更新、没开 ROW 格式、或在从库手动执行了 STOP REPLICA; INSERT ...; START REPLICA 的操作——它们会让复制状态彻底不可信。










