SQL 查询为什么慢?从执行计划 EXPLAIN 开始分析

在生产环境中,SQL 慢查询往往是数据库性能瓶颈的罪魁祸首。但很多人第一反应是加索引、改语句,却忽略了一个关键工具:EXPLAIN。

它就像 MySQL 提供的“透视眼”,能帮你直观地理解 SQL 执行背后的逻辑,精准找出瓶颈点。


一、EXPLAIN 是什么?

EXPLAIN 是 MySQL 提供的分析工具,它不会真正执行 SQL,而是输出执行计划,让你了解:

  • MySQL 会选择什么索引?

  • 是全表扫描还是范围查找?

  • 连接顺序如何?

  • 哪些操作开销最大?

语法非常简单:

EXPLAIN SELECT * FROM orders WHERE user_id = 123;

执行后你会看到一张表格,但关键是——你能读懂吗?


二、关键字段详解:EXPLAIN 表怎么看?

执行计划输出中的核心字段如下:

字段

含义

id

查询语句中每个 SELECT 的编号,值越大优先执行

select_type

表示当前 SELECT 的类型,如 SIMPLE、PRIMARY、SUBQUERY

table

当前访问的表名

type

连接类型,反映访问方式,性能从好到坏如下:

system > const > eq_ref > ref > range > index > all

possible_keys

可能使用的索引

key

实际使用的索引

rows

预估扫描的行数,越少越好

Extra

额外信息,如 Using index、Using filesort 等

这些字段串起来,就是你 SQL 被数据库“怎么走”的地图。


三、慢查询的常见信号

以下是一些常见的“高危信号”,一旦出现在 EXPLAIN 中,说明需要优化:

  • type = ALL:全表扫描,没有命中索引;

  • Extra 中包含 Using filesort:表示需要额外排序;

  • Extra 包含 Using temporary:说明用了临时表,可能导致内存开销大;

  • rows 数值很大:说明 MySQL 扫描了太多行来获取数据。


四、经典案例拆解

✅ 案例一:索引未命中

SELECT * FROM users WHERE LEFT(email, 5) = 'admin';

执行计划:

type: ALLkey: NULLExtra: Using where

📌 分析:虽然 email 上有索引,但函数包裹字段,导致索引失效,变成全表扫描。

✅ 优化建议:避免对索引字段进行函数运算,改为:

SELECT * FROM users WHERE email LIKE 'admin%';

✅ 案例二:多表连接效率差

SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.age > 30;

执行计划显示 orders 表先扫,再关联 users,但 users.age 无索引,导致大量回表操作。

📌 优化建议

  • 给 users.age 添加合适索引;

  • 或者通过优化连接顺序,让小表先执行。


五、EXPLAIN 不足之处

虽然 EXPLAIN 很强大,但也有局限:

  • 不显示真实执行耗时;

  • 无法反映临时表大小、缓存命中情况;

  • 对复杂 JOIN、子查询的执行顺序不一定精准;

  • 不包含执行过程中并发、锁等待信息。

✅ 建议搭配 SHOW PROFILE、performance_schema 或 EXPLAIN ANALYZE(MySQL 8.0+)使用。


六、实用建议

  1. 每条上线 SQL,务必使用 EXPLAIN 分析执行路径;

  2. 关注 type、key、rows、Extra 四大核心字段;

  3. 定期开启慢查询日志,结合执行计划逐条优化;

  4. 生产环境推荐开启 performance_schema 辅助诊断;

  5. 使用可视化工具如 Plan Explorer、Navicat 或 DataGrip 更直观分析执行计划。


七、EXPLAIN + ANALYZE:深度剖析真实执行流程(MySQL 8.0+)

在 MySQL 8.0 之前,EXPLAIN 只是静态预测执行路径,无法反映实际执行的耗时和行数。而 EXPLAIN ANALYZE 是 MySQL 8.0 引入的新特性,它真正执行 SQL 并输出真实的运行开销

EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123;

输出将包含:

  • 每一步实际读取的行数;

  • 每一步真实的执行耗时(单位微秒);

  • 判断是否使用了索引过滤(filtered);

  • 索引下推(Index Condition Pushdown, ICP)是否生效。

📌 实战价值:

  • 识别预测与实际执行的偏差;

  • 分析是否有非预期的回表、排序等操作;

  • 更精准地定位慢点在索引、连接、排序还是过滤。


八、索引失效的 10 种常见场景及优化策略

MySQL 使用不到索引时,查询性能常常雪崩。以下是最典型的十种失效场景及优化建议:

场景

描述

优化建议

1. 函数包裹字段

如 WHERE LEFT(name, 3) = 'abc'

改为 LIKE 'abc%'

2. 隐式类型转换

如 id = '123'(id 是 int)

参数类型要匹配

3. 使用 != 或 <>

不走索引范围查找

改为 NOT IN 或逻辑拆解

4. %前缀 模糊查询

LIKE '%abc' 无法使用索引

尽量避免左模糊

5. OR 条件跨字段

WHERE a = 1 OR b = 2

拆成 UNION

6. 字段参与表达式计算

score * 1.2 > 100

改为 score > 100 / 1.2

7. 隐式排序干扰

ORDER BY RAND()

尽量不使用

8. 复合索引顺序错乱

index(a, b),但 WHERE b = ?

严格按索引顺序写条件

9. NULL 比较

!= NULL 不合法

用 IS NOT NULL

10. MySQL 优化器判断失误

小表走索引反而慢

可通过 FORCE INDEX 强制指定索引


九、MySQL 8.x 优化器行为与 Query Rewrite 技巧

MySQL 8.x 的查询优化器引入了诸多行为变化与增强特性:

✅ 优化器增强示例:

  • 直方图统计:支持更精准的基数估算,提升索引选择准确性;

  • 更激进的 JOIN 重排序:减少数据量传递;

  • 持久化执行计划:避免频繁 SQL 重编译;

  • 窗口函数优化:允许更高效的执行计划。

✅ Query Rewrite(查询改写)技巧:

通过语法等价重写让优化器更容易生成理想执行路径。例如:​​​​​​​

-- 原始写法
SELECT * FROM users WHERE age + 1 = 30;
-- 改写后(避免表达式)
SELECT * FROM users WHERE age = 29;

也可通过 MySQL 的 Query Rewrite 插件实现自动改写,例如:

plugin-load-add = query_rewrite.so

配合规则定义文件重写慢 SQL。


十、基于 Cost-based Optimizer 的执行路径调优

MySQL 查询优化器采用基于成本的 Cost-Based Optimization(CBO)机制。它会评估每种可选路径的代价,然后选择“最便宜”的执行方式。

✅ 影响成本估算的关键因素:

  • 表数据分布和行数(使用 ANALYZE TABLE 更新统计信息)

  • 索引选择、范围 vs. 全扫描

  • 是否使用排序、临时表、中间结果集大小

  • JOIN 顺序与驱动表选择

✅ 常见调优思路:

1.强制使用预期索引

SELECT * FROM orders FORCE INDEX(idx_userid) WHERE user_id = 123;

2.手动调整 JOIN 顺序

SELECT STRAIGHT_JOIN ... -- 禁止优化器重排序

3.构造辅助列或冗余字段:帮助优化器走最优路径

4.更新统计信息

ANALYZE TABLE tablename;

📌 建议:复杂场景下使用 EXPLAIN ANALYZE + SHOW WARNINGS 配合观察优化器选择过程,逐步逼近最优解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小健学 Java

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值