根本原因是复制链路中某一步执行结果与主库实际变更不等价:包括非确定性SQL回放偏差、从库被误写或跳过错误、binlog格式与参数不匹配、GTID模式下SET SQL_LOG_BIN=0导致事务空洞。

MySQL主从复制出现数据不一致,根本原因不是“同步失败了”,而是**复制链路中某一步执行结果与主库实际变更不等价**。它不等于延迟大,也不等于线程挂了——哪怕 Slave_IO_Running 和 Slave_SQL_Running 都是 Yes,数据也可能早已分道扬镳。
SQL线程执行非确定性语句导致行级偏差
在 MIXED 或 STATEMENT 复制模式下,MySQL 会直接重放原始 SQL(比如 INSERT ... SELECT、UPDATE ... ORDER BY RAND())。但这类语句的执行结果依赖于:当前表状态、索引顺序、临时排序逻辑、甚至存储引擎的内部行为。
- 主库执行
INSERT INTO t SELECT * FROM s ORDER BY id LIMIT 10,可能按聚簇索引物理顺序取前10行; - 从库回放时,若
s表刚被其他事务修改过、或索引统计信息不同,ORDER BY id可能触发 filesort,结果集顺序变化,插入的其实是另外10行; - 自增主键值、触发器执行时机、函数(如
NOW()、UUID())在从库生成新值,也会造成字段不一致。
这种问题在初始化导入、批量补数、报表类写入中高频出现,且 Seconds_Behind_Master 完全无法反映。
从库被误写或跳过错误导致永久性缺失
只要从库没设 read_only=ON,任何有权限的账号都能直接写入。常见场景包括:
- 运维手动在从库执行
DELETE或UPDATE清理数据,未同步到主库; - 应用配置错误,部分读写流量路由到从库,产生脏写;
- 遇到报错(如
Error_code: 1032找不到记录、Error_code: 1062主键冲突)后,用SET GLOBAL sql_slave_skip_counter = 1硬跳过,跳过了本该删除/更新的那条语句,后续所有依赖该行的操作都失效。
这类不一致不会自动修复,且 SHOW SLAVE STATUS 中 Last_SQL_Error 可能已被覆盖,日志里只留一行“skipped”,极难追溯。
binlog格式与参数不匹配引发解析失败
即使开启 binlog_format=ROW,也不能保证绝对安全。以下配置组合会悄悄破坏 row event 的完整性:
-
max_allowed_packet在从库设得比主库小:主库生成的大事务 binlog event 被截断,从库 SQL 线程解析失败,可能静默丢弃部分变更; -
innodb_strict_mode=OFF+ 字段默认值不一致:主库插入INSERT INTO t (c1) VALUES (NULL),从库因缺失显式 DEFAULT 定义,把 NULL 转成 0 或空字符串; - 主库用
utf8mb4_0900_as_cs排序规则,从库是utf8mb4_general_ci:同样字符串在比较、去重、排序时行为不同,REPLACE或INSERT IGNORE结果不一致。
这类问题往往在跨版本升级、容器镜像基础环境不统一、或 DBA 手动调参后集中爆发,现象是某些表“偶尔少几条”,查日志却无报错。
GTID 模式下执行 SET SQL_LOG_BIN = 0 后未清理
在 GTID 复制中,SET SQL_LOG_BIN = 0 会跳过 binlog 记录,但事务仍会生成 GTID 并被写入 mysql.gtid_executed。如果之后又执行了 RESET MASTER 或手动清空该表,会导致:
- 从库认为某个 GTID 已执行,跳过对应事务;
- 但该事务实际未在从库执行(因为被
SQL_LOG_BIN = 0屏蔽),造成永久性空洞; -
SHOW SLAVE STATUS\G中Retrieved_Gtid_Set和Executed_Gtid_Set看似连续,实则中间缺了一段。
这种情况尤其危险:监控看不出异常,业务也跑得通,直到某天需要基于 GTID 做故障切换或搭建新从库,才发现数据对不上。
真正棘手的不一致,往往藏在“一切正常”的表象之下——没有报错、线程在跑、延迟为 0,但 checksum 工具一扫,几百张表里总有那么几张,COUNT(*) 对不上,MD5(CONCAT(...)) 校验失败。排查时别只盯着 Last_IO_Error,先确认复制模式、read_only 状态、GTID 执行历史,再决定是修还是重做。


















