升级前必须导出旧版本执行计划快照,用explain format=json保存完整计划(含cost_info、used_columns等),并记录analyze table后稳定统计信息及@@optimizer_switch值;升级后重点对比query_cost、access_type退化、using_temporary/filesort新增及handler_read_next异常增长,而非仅看type或rows变化。

升级前必须导出旧版本的执行计划快照
MySQL 9.6.0 对优化器行为做了实质性调整(比如外键约束上移至 SQL 层、cost_model 参数默认值变更),单纯靠“升级后跑一遍 EXPLAIN”无法判断变化是否由优化器逻辑导致。你得先在旧版本(如 8.0.x)中固化当前实际生效的执行路径。
实操建议:
- 对核心业务 SQL(尤其是慢查询日志里 top 10 的),用
EXPLAIN FORMAT=JSON导出完整计划,保存为plan_v8.json;不要只截图或记type/key字段,format=json包含cost_info、used_columns、access_type等底层决策依据 - 确保导出时表统计信息稳定:执行
ANALYZE TABLE table_name,避免因采样偏差导致计划波动 - 记录当时使用的
optimizer_switch值:SELECT @@optimizer_switch,新版本可能默认关闭某些旧特性(如semijoin)
升级后对比要聚焦 cost 和 access_type 差异
MySQL 9.6.0 的优化器更激进地倾向使用覆盖索引和哈希连接,但代价模型变了——rows 估算值可能大幅缩水,而实际 I/O 并未减少。不能只看 type 是否从 ALL 变成 ref 就认为变好了。
重点比对项:
-
query_cost字段(JSON 中的cost_info.query_cost):若新版本数值下降但响应时间反而上升,大概率是代价模型低估了磁盘随机读开销 -
access_type是否从range退化为index:9.6.0 在某些复合索引场景下会放弃范围扫描,改用全索引扫描+内存过滤,rows显示变小但filtered低于 20% 就危险 -
using_temporary或using_filesort是否新增:9.6.0 默认启用sort_buffer_size自适应,但若查询涉及UNION或窗口函数,可能触发更多临时表
验证阶段必须绕过 query cache 和 plan cache
MySQL 9.6.0 默认禁用 query_cache_type,但会缓存执行计划(prepared_statement_cache)。如果直接复用应用层预编译语句,看到的可能是旧计划的残留,而非真实优化器决策。
安全验证方式:
- 用
mysql -e "EXPLAIN ..."直连执行,避免客户端驱动缓存 - 加
/*+ NO_CACHE */hint 强制刷新计划缓存(9.6.0 支持该 hint) - 检查
SHOW STATUS LIKE 'Handler_read%':若Handler_read_next暴增而Handler_read_key下降,说明索引跳过效率变差,即使EXPLAIN显示用了索引
遇到 plan regression 要优先检查统计信息精度
9.6.0 默认将 innodb_stats_persistent_sample_pages 提高到 100(旧版常为 20),但如果你的表有严重数据倾斜(比如 90% 订单集中在最近 7 天),新采样可能误判高频值分布,导致优化器选错索引。
临时对策:
- 对关键大表手动指定采样页数:
ALTER TABLE orders STATS_SAMPLE_PAGES=200 - 用
INFORMATION_SCHEMA.STATISTICS检查SEQ_IN_INDEX和CARDINALITY是否与实际区分度匹配(例如status字段CARDINALITY是 3,但实际只有 'pending'/'done' 两种值,就是统计失真) - 回退到旧版统计逻辑:
SET SESSION innodb_stats_method='nulls_unequal',避免 NULL 值干扰基数估算
真正麻烦的不是计划变了,而是变完之后没人盯着 Handler_read_rnd 和 Innodb_buffer_pool_wait_free 这两个指标——它们才暴露真实 I/O 压力。











