主从EXPLAIN结果不同源于优化器决策依据差异:统计信息陈旧、optimizer_switch不一致、read_only不限制临时表、binlog_format非ROW等导致执行计划漂移。

为什么主从上 EXPLAIN 结果不一样?
主从同步本身不保证执行计划一致——MySQL 优化器基于统计信息、索引分布、配置参数和实际数据分布做决策,而这些在主从间天然可能不同。最常见诱因是:从库没开 innodb_stats_persistent,或 innodb_stats_auto_recalc 关闭后统计信息长期未更新,导致从库用的是过期直方图或采样估算。
实操建议:
- 主从都设
innodb_stats_persistent = ON,并确认innodb_stats_auto_recalc = ON(5.6.6+ 默认开启,但升级或手动配置时容易漏) - 检查
information_schema.INNODB_TABLESTATS和INNODB_INDEXSTATS表的last_update字段,对比主从是否严重滞后 - 若业务允许,可在从库执行
ANALYZE TABLE强制刷新(注意:会加表级读锁,大表慎用)
slave_parallel_type = LOGICAL_CLOCK 下为何还卡在单线程回放?
即使开了并行复制,只要事务之间存在逻辑依赖(比如同一张表的写操作被分到不同事务但有锁等待),从库仍会退化为串行回放。更隐蔽的问题是:主库 binlog_format 不是 ROW,或用了 STATEMENT 模式混写,导致 GTID event 分组失效,LOGICAL_CLOCK 失去判断依据。
实操建议:
- 确认主库
binlog_format = ROW,且所有写入(包括应用层、中间件、定时任务)都不绕过该设置 - 查从库状态:
SHOW SLAVE STATUS\G中看Slave_SQL_Running_State是否频繁出现Waiting for preceding transaction to commit - 临时启用
slave_preserve_commit_order = ON(8.0.13+)可缓解乱序提交引发的阻塞,但会轻微增加延迟
从库 read_only = ON 但还是能写进临时表?
read_only 默认不限制 TEMPORARY 表创建,也不阻止 CREATE TEMPORARY TABLE 或 INSERT INTO ... SELECT 这类隐式临时表操作。这类行为不会写入 binlog,但会污染从库的查询缓存、影响 EXPLAIN 的“真实”执行路径(比如优化器误判临时表大小),进而导致主从执行计划偏差。
实操建议:
- 严格设置
super_read_only = ON(需先开read_only),它才真正禁止所有写操作,包括临时表 - 监控从库是否频繁出现
Created_tmp_tables增长(用SHOW GLOBAL STATUS LIKE 'Created_tmp%'),异常高说明有应用在从库跑分析类 SQL - 应用层避免在从库执行
SELECT ... INTO OUTFILE、SELECT ... INTO DUMPFILE等带写副作用的语句
主从 optimizer_switch 不一致引发计划漂移
哪怕 MySQL 版本相同,optimizer_switch 默认值也可能因编译选项或补丁版本微调而不同。比如 index_merge=on 在主库生效、从库关闭,就可能导致主走索引合并、从走全表扫描;又或者 condition_fanout_filter=off 导致从库低估连接结果集大小,选错驱动表。
实操建议:
- 主从都显式设置统一值:
SET PERSIST optimizer_switch = 'index_merge=on,index_merge_union=on,...';(8.0.22+ 支持PERSIST,否则改my.cnf并重启) - 用
SELECT @@optimizer_switch;对比主从输出,逐项核对,尤其关注materialization、semijoin、loosescan这些影响较大的开关 - 上线前在从库执行
EXPLAIN FORMAT=TREE(8.0.16+)对比主库输出,比传统EXPLAIN更直观暴露优化器决策差异
执行计划不一致从来不是孤立现象,它背后往往是统计信息陈旧、配置项错位、或写操作逃逸了只读约束。最容易被忽略的,是那些“看起来不影响同步”的配置——比如 tmp_table_size 主从不一致,会让同样一条 GROUP BY 在从库被迫落磁盘,触发不同的排序算法选择,最终改变执行路径。


















