在生产环境中,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: ALL
key: NULL
Extra: 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+)使用。
六、实用建议
-
每条上线 SQL,务必使用 EXPLAIN 分析执行路径;
-
关注 type、key、rows、Extra 四大核心字段;
-
定期开启慢查询日志,结合执行计划逐条优化;
-
生产环境推荐开启 performance_schema 辅助诊断;
-
使用可视化工具如 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 配合观察优化器选择过程,逐步逼近最优解。