mysql主从复制默认不保证强一致性,因其采用异步机制:主库提交事务后才发送binlog至从库,且从库sql线程回放存在延迟,导致“先写后读”可能读到旧值或空值。

主从复制为什么默认不保证强一致性
MySQL 主从复制是异步的,binlog 写入主库后才发给从库,中间存在时间差;从库 SQL Thread 回放也可能延迟。这意味着:主库提交事务后立刻读从库,很可能读不到最新数据,甚至读到旧值或空值。这不是 bug,而是设计取舍——异步复制优先保障写入性能,而非实时一致性。
常见错误现象包括:select 刚插入的数据查不到、分页错乱、状态变更未同步就触发下游逻辑。
- 主库
INSERT后立即在从库SELECT,返回空 - 应用层“先写后读”场景(如注册后跳转个人页)出现 404 或旧信息
-
SHOW SLAVE STATUS中Seconds_Behind_Master持续 > 0
用 semi-sync 复制降低不一致窗口
半同步(semi-sync)要求至少一个从库将 binlog 写入 relay log 并刷盘后,主库才返回成功。它不能消除延迟,但能避免“主库提交了,但从库一条都没收到”的极端不一致。
启用前需确认:从库已安装 rpl_semi_sync_slave 插件,主库安装 rpl_semi_sync_master;且 timeout 参数不宜设为 0(否则主库会卡死)。
- 主库执行:
SET GLOBAL rpl_semi_sync_master_enabled = 1; - 从库执行:
SET GLOBAL rpl_semi_sync_slave_enabled = 1; - 检查状态:
SHOW VARIABLES LIKE 'rpl_semi_sync%';和SHOW STATUS LIKE 'Rpl_semi_sync%'; - 注意:
rpl_semi_sync_master_timeout建议设为 1000–10000(毫秒),超时后自动退化为异步
应用层读写分离必须做一致性路由
如果业务代码直接连多个 MySQL 实例,又没控制读写路径,很容易在写完主库后误读从库旧数据。最稳妥的方式不是靠 DB 层“等同步”,而是让应用自己决定该读谁。
典型做法是:对刚写入的记录,强制走主库查询(例如通过上下文标记、连接 hint 或注释);或者利用 GTID + WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS() 主动等待从库追上。
- 写操作后需立即读同一行?加
/*+ USE_MASTER */注释(需代理或中间件支持) - 用 GTID 场景下,主库写完可调
SELECT WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS('xxx-yyy-zzz:123');等从库回放完成 - 避免依赖
SELECT SLEEP(0.1)这类拍脑袋延迟,不可靠且伤性能 - 若用
MyCat/ShardingSphere等中间件,务必开启sqlHint或master-slave-route路由策略
监控和兜底:及时发现并定位不一致
复制不一致往往不会报错,而是静默发生。靠人工巡检几乎不可能,必须建立自动化检测机制。
核心手段是比对主从的 binlog position 或 GTID_EXECUTED 集合,并结合表级校验工具定期扫描。尤其要注意大事务、DDL、SET sql_log_bin=0 等绕过复制的操作。
- 检查复制状态:
SHOW SLAVE STATUSG关注Slave_IO_Running、Slave_SQL_Running、Retrieved_Gtid_Set与Executed_Gtid_Set是否一致 - 用
pt-table-checksum工具做表数据一致性校验(需主从都开启binlog_row_image=FULL) - 禁止在从库执行
STOP SLAVE; INSERT/UPDATE; START SLAVE;—— 这会导致 GTID 冲突或数据覆盖 - 从库
read_only=ON必须开启,但注意它不阻止 super 用户写入,必要时加super_read_only=ON
实际中最容易被忽略的是:把“主从延迟低”等同于“数据一致”。即使 Seconds_Behind_Master = 0,也只代表 SQL Thread 当前没积压,不代表刚刚发生的事务已落盘或已提交。真正需要强一致的场景,得靠应用控制读写路径,而不是赌复制快。