mysql 8.0升级后执行计划劣化大概率源于optimizer_switch默认值变更,需对比新旧版本select @@optimizer_switch输出,重点关注hash_join=on、derived_merge=on等新增开启项,并用explain format=json定位优化阶段变化。

MySQL 8.0升级后SQL执行计划突变,大概率不是SQL写错了,而是optimizer_switch里某些开关默认值变了——优化器“换脑子”了,但没告诉你。
怎么确认是optimizer_switch导致的执行计划劣化
别靠猜。直接对比升级前后的实际开关状态:
- 在旧库(5.7)和新库(8.0)分别执行
SELECT @@optimizer_switch;,把输出结果并排比对 - 重点关注带
=on或=off的项,比如hash_join、derived_merge、mrr、use_index_extensions - 如果某条慢SQL在5.7走的是
ref+ 索引,8.0变成ALL,且hash_join=on是新增的,那很可能是哈希连接触发了更重的内存开销或错误估算 - 用
EXPLAIN FORMAT=JSON查看query_block下的optimization_phase和chosen_plan字段,能定位到具体哪个规则被启用/禁用
optimizer_switch里哪些开关最常踩坑
这几个开关在5.7→8.0升级中默认变更最多,也最容易让老SQL翻车:
-
hash_join=on:8.0默认开启。对大表JOIN可能比BNL快,但若join_buffer_size太小或统计信息不准,反而退化成多次扫描 -
derived_merge=on:8.0默认开启。会把子查询自动合并进外层,但有时破坏原有索引利用路径(比如子查询有ORDER BY LIMIT,合并后无法下推) -
mrr=on:8.0默认开启。对范围查询+回表有优化,但若read_rnd_buffer_size过小,MRR反而比传统回表更慢 -
use_index_extensions=on:InnoDB默认开启,允许优化器使用隐藏的主键扩展列做索引覆盖判断——但若表有大量冗余二级索引,可能误判“覆盖”而放弃更优路径
临时验证和长期修复怎么做
先快速验证是否是某个开关惹的祸,再决定要不要永久调整:
- 对单条SQL临时关闭可疑开关:
SET optimizer_switch='hash_join=off';,再跑EXPLAIN看执行计划是否回归 - 不要全局关掉整个
optimizer_switch字符串,只改关键项,避免影响其他正常SQL - 若确认是某开关导致问题,优先查根本原因:比如
hash_join出问题,先检查join_buffer_size是否够、关联字段是否有索引、统计信息是否最新(ANALYZE TABLE) - 生产环境不建议长期依赖
SET optimizer_switch会话级修改——连接池复用、应用重启后容易失效;应评估是否需在my.cnf里固化,或重构SQL绕过该路径
真正难处理的不是开关本身,而是它和统计信息、索引设计、内存配置耦合在一起——关一个开关可能缓解症状,但不更新INNODB_TABLESTATS,下次数据倾斜时还会复发。











