第2章:理解执行计划——用EXPLAIN看穿查询

说实话,我刚做优化那会儿,面对一条慢查询,经常是两眼一抹黑。加索引靠猜,改SQL靠蒙。后来带我的老大哥甩了一句:“你连执行计划都不看,优化个什么劲?”这句话我记到现在。

执行计划,说白了就是数据库告诉你“我打算怎么查这条数据”。用什么索引、先查哪张表、数据怎么拼接,全写在里面。看不懂它,优化就是瞎忙活。

2.1 一条EXPLAIN,揭开执行全貌

用法简单。在你的查询前面加上EXPLAIN,就能看到执行计划。拿我最近调优的一个订单查询举例:


EXPLAIN SELECT o.order_id, o.amount, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'PAID'
ORDER BY o.created_at DESC
LIMIT 20;

输出会给你一张表格。这张表里藏着所有秘密。我习惯一行一行去读,重点看几个关键字段。

核心原则:执行计划从上往下读,最上面的行是驱动表。驱动表的选择,决定了整个查询的基本效率。

2.2 关键字段,看穿优化点

EXPLAIN输出字段不少,但咱们优化实战,盯住这几个就够用了。我整理了一个速查表:

字段 含义 重点关注信号
id 查询的序号,id相同则从上往下执行,id越大越优先执行。 id越大越应该优先优化
select_type 查询类型。SIMPLE是简单查询,PRIMARY是主查询,SUBQUERY是子查询。 出现DEPENDENT SUBQUERY要小心
type 访问类型,这是最核心的字段。从好到差依次是:system > const > eq_ref > ref > range > index > ALL。 出现ALL就要报警了
possible_keys 可能用到的索引。 如果NULL,说明没索引可用
key 实际使用的索引。 对比possible_keys,看有没有选错
rows 预估扫描的行数。 这个数字越大,查询越慢
Extra 额外信息,藏着优化线索。 Using filesort、Using temporary是性能杀手
我的小技巧:先把type和rows圈出来。我一般要求生产环境的查询type至少达到ref级别,rows超过一万行就必须优化。

2.3 type字段:访问类型的优化阶梯

在type字段里,ALL代表全表扫描。这是最糟糕的情况。我一个同事曾经遇到过这样的场景:


EXPLAIN SELECT * FROM orders WHERE status = 'PAID';
-- type: ALL, rows: 500000

扫描50万行,就为了查几笔订单?这肯定不行。加个索引就能把type从ALL提升到ref:


ALTER TABLE orders ADD INDEX idx_status (status);
-- 加完索引后再看EXPLAIN,type变成了ref,rows降到了2000

你看,一条SQL的执行效率,从全表扫描降到索引查找,靠的就是看懂type列。我每做一次优化,都会对比优化前后的type值,这个习惯让我少走了很多弯路。

注意:不是所有ALL都是坏的。如果表里只有几百行数据,全表扫描可能比走索引还快。我在项目里遇到过新手乱加索引,结果写入性能反而下降了。

2.4 解读Extra:帮你定位性能瓶颈

Extra列说白了就是数据库给你的一些“额外提示”。我重点解释三个最常见的:

  • Using filesort:排序没走索引。比如你ORDER BY的列没有索引,数据库就会在内存或磁盘里做一次额外排序。解决方法是加联合索引,把ORDER BY的列包含进去。
  • Using temporary:使用了临时表。常见于GROUP BY、DISTINCT等操作。我遇到过一个大屏报表查询,就是因为GROUP BY没走索引,导致临时表占满了磁盘空间。
  • Using index:这其实是好消息。说明查询只扫描了索引,不需要回表查数据。能做到这一点的查询,性能通常都很快。

我曾经接手一个慢查询,Extra显示Using filesort; Using temporary,这两个杀手同时出现。改索引后,查询时间从3秒降到了0.1秒。优化点很简单:加了一个包含status、created_at、user_id的联合索引。

2.5 关联查询:注意驱动表和被驱动表

多表JOIN时,执行计划会告诉你哪个是驱动表(第一行),哪个是被驱动表(第二行)。优化原则是:驱动表用小表,被驱动表上用索引。

来看看下面这个例子:


EXPLAIN SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.city = '北京';

如果users表有10万行,orders表有500万行,而且users.city有索引。执行计划可能会先查users(驱动表),再查orders(被驱动表)。但如果orders.user_id没有索引,那被驱动表就得做全表扫描,每次查出50万行数据,反复10万次?那性能就崩了。

避坑指南:我曾经因为没注意被驱动表的索引,把一个报表查询从1秒跑成了2分钟。检查执行计划后发现,被驱动表的关联字段根本没建索引。一个索引加上去,查询就秒回了。

2.6 本章核心知识体系

为了方便你建立整体认知,我画了一张图,把EXPLAIN分析的核心逻辑串起来:

EXPLAIN 分析执行计划核心流程 输入慢查询SQL 执行 EXPLAIN type 访问类型 system > const > ref rows 预估扫描行 数字越大越慢 Extra 额外信息 filesort / temporary 分析瓶颈,定位优化点 制定优化方案 规律:type > rows > Extra 逐项排查

这张图展示了我个人的分析套路:从慢查询出发,执行EXPLAIN,再聚焦type、rows、Extra这三个核心输出,最后找到优化点。你想想看,是不是一条很清晰的主线?

2.7 实战总结

讲到这里,我想说几句掏心窝子的话。执行计划这东西,光看不练是没用的。我建议你养成一个习惯:每次上线新SQL之前,先跑一遍EXPLAIN。别管查询多简单,看一眼type是几,扫一眼rows多大。

日常自检清单:
  • type:有没有ALL?有就加索引或改SQL。
  • rows:预估行数是否超过业务预期?
  • Extra:出现Using filesort或Using temporary,立刻改索引。
  • key:实际用到的索引,是不是你预期的那一个?
  • possible_keys:如果为NULL,说明表上连可用索引都没有。

记住,EXPLAIN是你优化道路上的火眼金睛。学会看它,你的优化效率至少提升3倍。