主从复制为什么默认不保证强一致性
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 STATUS\G关注
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 当前没积压,不代表刚刚发生的事务已落盘或已提交。真正需要强一致的场景,得靠应用控制读写路径,而不是赌复制快。
