第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值,这个习惯让我少走了很多弯路。
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万次?那性能就崩了。
2.6 本章核心知识体系
为了方便你建立整体认知,我画了一张图,把EXPLAIN分析的核心逻辑串起来:
这张图展示了我个人的分析套路:从慢查询出发,执行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倍。