mysql 5.7升级到8.0后explain执行计划变差,主因是统计信息陈旧、cost model重估及optimizer_switch默认变更叠加导致优化器误判;需立即analyze table、检查last_update时间、验证直方图并针对性调整索引与开关设置。

EXPLAIN 显示的执行计划变了,不是 MySQL 8.0 “变坏了”,而是它换了一套更依赖数据真实性的决策逻辑——旧统计信息、默认开关变更、成本模型重估,三者叠加,让优化器“重新认人”时认错了。
为什么 EXPLAIN 的 type 从 ref 变成 ALL?
这是最直接的信号:优化器误判了索引选择性。5.7 升级到 8.0 后,INNODB_TABLESTATS 和 INNODB_INDEXSTATS 中的 last_update 时间仍停留在升级前,导致它看到的行数估算(rows)严重偏离真实值。
- 查证方式:
SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLESTATS WHERE TABLE_NAME = 'your_table',看last_update是否早于升级时间 - 交叉验证:
SHOW INDEX FROM your_table查Cardinality,再对比SELECT COUNT(DISTINCT your_col) FROM your_table;差 10 倍以上基本确认失真 -
innodb_stats_auto_recalc=ON不是万能的——它只对单次变更超 10% 行数的表触发,冷表永远不动
为什么 ORDER BY 突然走 Using filesort?
8.0 默认启用更激进的内存排序策略,同时废弃了 max_length_for_sort_data,不再按字段长度切分排序模式。结果就是:原来靠索引有序扫描的查询,现在一律走 filesort,一旦 sort_buffer_size 不够或字段太长,立刻写磁盘临时文件。
- 判断是否真走索引排序:
EXPLAIN输出中Extra列不含Using filesort才算 - 临时修复:补全复合索引,例如
WHERE a=1 ORDER BY b,c就建INDEX(a,b,c) - 别用
SELECT *加剧恶化——字段越多,filesort内存压力越大;可先试SELECT a,b,c看是否恢复
为什么 FORCE INDEX 失效,执行计划里冒出 using_index_merge?
8.0.19+ 默认开启 index_merge=on,优化器会主动把多个单列索引“合并使用”,哪怕你写了 FORCE INDEX (idx_composite),它也可能无视并改走 idx_a,idx_b 合并路径。
- 确认方式:在
EXPLAIN FORMAT=TREE或EXPLAIN ANALYZE输出里搜using_index_merge - 临时绕过:
IGNORE INDEX (idx_a, idx_b)把它想合并的单列索引全禁掉 - 根本解法:删掉冗余单列索引——比如已有
(a,b)复合索引,就别留(a)或(b)单列索引,减少优化器的“错误联想”
为什么 rows 没变但实际变慢了?
MySQL 8.0 默认启用新的 cost_model,它基于表统计信息(如页数、索引基数、直方图)动态计算 IO/CPU 成本。如果没收集直方图或统计信息陈旧,优化器可能严重低估排序/临时表/JOIN 的成本,从而放弃走索引、改用全表扫描 + Using filesort。
- 检查直方图:
SELECT * FROM information_schema.COLUMN_STATISTICS WHERE SCHEMA_NAME = 'db_name' AND TABLE_NAME = 'tbl_name' - 强制更新:
ANALYZE TABLE tbl_name UPDATE HISTOGRAM ON col1, col2(仅 8.0.19+ 支持) - 快速验证:
SET SESSION optimizer_use_condition_selectivity=1回退到传统估算,看是否恢复
真正要盯住的,不是版本号,而是 ANALYZE TABLE 是否跑过、EXPLAIN FORMAT=TREE 里有没有不该出现的 materialize 或 using_index_merge 节点,以及那些被你忽略的 last_update 时间戳——它们才是执行计划突变的真实推手。











