MySQL 8.0 迁移后 SQL 变慢:先看 EXPLAIN

MySQL 8.0 迁移后 SQL 变慢,不等于数据量或硬件一定出了问题,也不应该一上来就批量加索引、全局关闭优化器。更可靠的排查顺序是:锁定具体慢 SQL,保存迁移前后的 EXPLAIN,比较访问路径、行数估算和排序/回表成本;确认统计信息失真后先更新统计信息,再决定是否调整索引、SQL 写法或优化器开关。

判断执行计划时,不要只盯着 key 是否变化。同一组参数、同一段 SQL,在可比的数据分布下,应把 key/type、rows/filtered 和 Extra 放在一起看。如果计划没有明显变化,估算与实际扫描量也接近,就应转向锁等待、监控 SQL 和日志 I/O,而不是继续折腾索引。

下面这些命令需要替换尖括号内容,不能原样执行:

EXPLAIN <original_sql>;

SHOW VARIABLES LIKE 'log_queries%';
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'log_output';

如果慢日志写入 mysql.slow_log,可以利用其中的 query_time、rows_sent、rows_examined 和 sql_text 找到真正拖慢业务的语句,再把对应的 rows_examined 与 EXPLAIN 估算放在一起核对。 资料中的 RDS 示例还涉及参数组和重启,这是该托管环境的配置方式,不能直接套到自建实例或其他云数据库上。

EXPLAIN 对照:计划究竟变了哪一项

迁移前后的执行计划对照,可以归纳成三组信息:

对照项 重点观察 可能的含义
key、type 选了哪个索引,采用什么访问方式 优化器更换了访问路径,候选索引的扫描成本发生了变化
rows、filtered 预计扫描规模及过滤后的比例 统计信息可能与当前数据分布不匹配,估算偏差也可能继续传递到后续连接
Extra 是否增加排序等额外处理 例如出现 Using filesort,说明计划增加了排序步骤

判断时,key 和 type 反映访问路径,rows 和 filtered 反映优化器对扫描规模及过滤比例的估算,Extra 反映排序等额外处理。只看 key,很容易把一次合理的访问路径调整误判成索引退化;只看 rows,又可能漏掉排序与回表带来的额外成本。

例如,迁移后索引名称没有变化,但 rows 从较小规模突然涨到接近表级扫描量,优先怀疑统计信息没有跟上数据导入;如果 key 和 type 发生变化,则要比较聚簇主键扫描、二级索引范围扫描及回表成本;如果 Extra 新增 Using filesort,则要结合过滤后的数据量、ORDER BY、GROUP BY、LIMIT 和时间范围条件判断排序是否已经成为主要成本。

EXPLAIN 中的行数是优化器基于统计信息得到的估算,不是实际扫描行数。慢日志中的 rows_examined 可以提供另一侧证据:如果估算只有少量行,实际却扫描了大部分表,问题多半不只是“少了一个索引”,而是统计信息、数据相关性或访问路径需要一起检查。

如果迁移前的执行计划已经丢失,可以在测试环境用代表性数据重建对照,但要尽量保持数据分布、SQL 参数和表结构可比。仅凭新计划中出现了另一个索引名称,无法证明变慢就是由它造成的。

统计信息与索引成本:先修正估算,再决定是否动索引

批量导入完成后,数据分布已经变了,统计信息却可能仍反映导入前或导入过程中的状态。此时最常见的表现不是完全不走索引,而是 rows 暴增、filtered 异常,或者优化器基于失真的估算选择了不合适的访问路径。

先更新目标表的统计信息,再重新执行 EXPLAIN:

ANALYZE TABLE <table_name>;
SHOW INDEX FROM <table_name>;

ANALYZE TABLE 的锁行为、在线能力和具体影响与 MySQL 8.0 小版本、表引擎有关,需要按目标版本确认。不要把它描述成绝对无锁,也不要在未评估影响时直接放到业务高峰执行。

如果更新统计信息后估算恢复正常,但计划仍然不理想,才进入索引成本的取舍。聚簇主键在过滤命中率很低、返回列又很多的情况下,未必比二级索引加大量回表更贵;覆盖索引则可能通过减少回表,抵消更大索引体积带来的成本;反过来,二级索引如果需要随机访问并逐行回表,优化器也可能放弃它。

SQL 写法同样会改变索引能否被利用。例如,条件列参与运算、使用用户变量时,普通索引可能无法按预期匹配;如果同时存在 ORDER BY 和 LIMIT,优化器还可能为了减少排序,选择沿聚簇索引反向扫描。 对这类查询,应先判断能否在保持业务语义不变的前提下改写为可匹配索引的条件,再比较改写前后的执行计划和实际耗时。

Using filesort 也不等于“缺一个索引”这么简单。它说明计划中出现了额外排序步骤,是否值得增加排序索引,要结合过滤后的规模、排序字段和返回行数判断;范围条件、GROUP BY 和 LIMIT 都可能改变最终收益。 新增索引会引入存储与后续写入维护成本,因此至少应通过同一数据集验证响应时间、实际扫描行数和并发表现,再决定是否保留。

迁移后的特殊变化:优化开关、监控 SQL 与日志开销分开查

优化器开关需要单会话 A/B 对照,不能凭版本号直接套用。先查看当前实例实际支持的开关:

SHOW VARIABLES LIKE 'optimizer_switch';

-- 仅在确认当前版本支持该开关后,用于单次 A/B 对照
SET optimizer_switch = 'prefer_ordering_index=off';

hash_join、derived_merge 和 prefer_ordering_index 是否存在、默认值是什么,需要按目标 MySQL 8.0 小版本确认;开关不存在时不要强行写入。prefer_ordering_index=off 只用来验证“是否因为偏好能够满足排序的索引而改变访问路径”,不是生产修复命令。对照结束后应结束测试会话,不要把临时设置写入全局配置。

监控 SQL 需要单独排查。资料中的升级案例从 MySQL 5.7.29 升到 8.0.25 后,sys.innodb_lock_waits 引用的底层表及其行为发生变化,最终造成业务 SQL 堆积;当时服务器 CPU、磁盘 I/O 和网络负载都处在较低水平。这只能说明视图或底层对象的行为变化可能制造阻塞,不能推广为所有升级变慢都由 sys.innodb_lock_waits 引起。

日志也可能制造额外的 I/O 压力。例如 log_queries_not_using_indexes 开启后,未使用索引的查询可能持续写入慢日志;迁移后执行计划改变时,这类记录量还可能进一步放大。 因此,当多条 SQL 同时变慢时,应检查慢日志开关、输出目标和日志写入量,不能把所有延迟都归到某条业务 SQL 的索引上。

JSON_EXTRACT 等函数查询则应建立独立复现用例。相关 issue 中出现过针对特定工作流数据的临时实现,但它不是可以直接推广到所有 MySQL 8.0 环境的通用修复,也不能跳过目标版本和数据分布验证。

如果新旧执行计划一致、估算与实际扫描量接近,而 SQL 仍然变慢,就应转查锁等待、监控任务和慢日志写入压力。统计信息更新不能解决所有表达式与 JSON 查询问题,优化器开关也不能替代索引设计和 SQL 改写;最终方案是否上线,仍要在目标小版本、真实数据分布和实际并发下验证。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容