innodb_stats_persistent_sample_pages调低反而让执行计划更稳是错误认知;实际是默认值20在数据倾斜时易致基数估算失真,引发索引误判或全表扫描,应根据数据分布适度调高至100~200并配合ANALYZE TABLE,MySQL 8.0起优先用直方图替代。

为什么innodb_stats_persistent_sample_pages调低反而让执行计划更稳
MySQL 5.7 默认 innodb_stats_persistent_sample_pages = 20,但这个值在数据分布不均(比如某索引前10%值占了90%行)时,采样页太少会导致基数(Cardinality)估算严重失真。优化器一看“这个索引选择性很差”,就跳过它,转而全表扫描——哪怕你加了索引也白搭。
实操建议:
- 先查真实倾斜程度:
SELECT COUNT(DISTINCT col) / COUNT(*) FROM table_name,若结果 - 把采样页数提高到
100或200(上限默认是2000),命令:SET GLOBAL innodb_stats_persistent_sample_pages = 200; - 必须配合
ANALYZE TABLE才生效,只改参数不刷新统计信息毫无作用 - 注意:该变量仅对启用了
innodb_stats_persistent = ON的表有效(5.7 默认开启)
执行计划变来变去,ANALYZE TABLE 后还是不准怎么办
不是所有表都适合用默认采样策略。尤其当表里有大量 NULL、空字符串、或 JSON 字段(5.7 中 JSON 的 CARDINALITY 计算本身就不准),ANALYZE TABLE 可能反复给出矛盾结果。
这时要人工干预:
- 禁用自动重采样:
SET GLOBAL innodb_stats_auto_recalc = OFF;,防止写入触发不可控更新 - 用
FORCE强制指定采样页数:ANALYZE TABLE t1 PERSISTENT FOR ALL;(需先设好innodb_stats_persistent_sample_pages) - 对关键索引单独验证:
SHOW INDEX FROM t1;对比Cardinality和SELECT COUNT(DISTINCT idx_col) FROM t1,差10倍以上就得调参重刷 - 避免在业务高峰跑
ANALYZE TABLE,它会读取聚簇索引页,可能引发短暂锁等待
optimizer_switch 里哪些开关会影响执行计划稳定性
5.7 的 optimizer_switch 默认开启一堆规则,其中几个和执行计划抖动强相关:
-
condition_fanout_filter=on:启用后优化器会预估条件过滤率,但对复杂IN或OR表达式容易误判——关掉它常能让EXPLAIN更可预测 -
derived_merge=on:子查询合并可能改变访问路径,尤其当子查询含LIMIT或GROUP BY;临时关掉可快速验证是否由此引发波动 -
index_condition_pushdown=on:一般有益,但在某些复合索引+范围查询组合下,反而让优化器放弃使用索引;可对比开/关时的key_len和rows - 修改方式:
SET GLOBAL optimizer_switch='condition_fanout_filter=off,derived_merge=off';,别直接覆盖整个值
为什么加了索引、ANALYZE TABLE 也做了,EXPLAIN 还是选错索引
执行计划“选错”不一定是统计信息问题,5.7 存在几个隐蔽但高频的干扰点:
-
SQL_NO_CACHE不影响执行计划,但会让EXPLAIN结果看起来“不稳定”——其实是缓存层掩盖了真实执行路径,务必在关闭查询缓存下测试 - 隐式类型转换:比如
WHERE varchar_col = 123,会导致索引失效且优化器误判成本;检查EXPLAIN的type是否为ALL或range突然变ref - 分区表的统计信息是按分区单独维护的,
ANALYZE TABLE只刷新主表元数据,得用ANALYZE TABLE t1 PARTITION(p0);逐个刷 - 如果用了
FORCE INDEX但执行计划仍漂移,大概率是 MySQL 在 prepare 阶段缓存了错误计划——执行FLUSH TABLES清除语句缓存


















