凌晨两点,你被报警电话吵醒。线上数据库CPU飙升,接口响应时间从200ms涨到了8秒。老板在群里疯狂@你,你打开数据库一顿操作,敲了一行命令:
SELECT * FROM orders WHERE user_id = 12345 AND status = "paid" ORDER BY created_at DESC LIMIT 20;
Explain一下?好,你explain了。然后你看着输出的10行数据陷入了沉思——type: ALL,rows: 500000,Using filesort。你知道这不妙,但你跟老板说不清楚。
今天我们来彻底搞定EXPLAIN,看完这篇,你也能甩回去一个眼神让他闭嘴。
EXPLAIN不是只看type的
很多人看EXPLAIN只看两样东西:type和rows。type是ALL就说要加索引,rows太多就说数据量大。这没错,但太浅了。就像看体检报告只盯着白细胞看,其他指标全略过——你可能会错过真正的问题。
先建一张测试表:
CREATE TABLE order_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT DEFAULT 1,
price DECIMAL(10,2),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_order_id (order_id),
INDEX idx_product_id (product_id),
INDEX idx_created (created_at)
) ENGINE=InnoDB;
然后插点数据,跑个查询:
EXPLAIN SELECT * FROM order_items
WHERE product_id = 100
ORDER BY created_at DESC
LIMIT 10;
典型输出:
id: 1
select_type: SIMPLE
table: order_items
type: ref
possible_keys: idx_product_id
key: idx_product_id
key_len: 8
rows: 1200
filtered: 10.00
Extra: Using where; Using index; Backward index scan
光看type=ref感觉还行,但rows=1200,filtered=10%——这意味着要扫1200行才拿到10条结果。过滤性很差,索引选得不够精准。
key_len:你以为选对了索引,其实没有
这是最容易忽略的字段。key_len告诉你实际上这个查询用了多少字节的索引。
看这个例子:
ALTER TABLE order_items ADD COLUMN name VARCHAR(255) NOT NULL DEFAULT "";
假设有一个联合索引:
INDEX idx_name_product (name, product_id)
然后查:
EXPLAIN SELECT * FROM order_items
WHERE name = "张三" AND product_id = 100;
如果key_len=257,说明只用了name那一列(VARCHAR(255)变长,1~2字节头 + 实际长度),product_id根本没进索引。
为什么?因为MySQL优化器认为255长度的VARCHAR已经足够特殊了,没必要再拼上product_id。
key_len的正确看法:数值越小,说明索引被用得越少。如果你的查询明明有两个条件,但key_len只有一个字段的长度,那第二个条件根本没进索引。
Using filesort不一定是坏事——但你要知道什么时候是
一说filesort,全公司都要给你加索引。这太片面了。
MySQL的filesort有两种模式:
模式一:索引覆盖的filesort
当ORDER BY的列正好在索引里,且SELECT的列也在同一索引中,MySQL可以按索引顺序直接返回,连filesort都懒得用了。
EXPLAIN SELECT id, created_at FROM order_items
WHERE product_id = 100
ORDER BY created_at DESC;
Extra里写的是Using index——这叫索引覆盖扫描(Covering Index),性能极好,不需要回表,filesort?不存在。
模式二:回表后的filesort
EXPLAIN SELECT * FROM order_items
WHERE product_id = 100
ORDER BY created_at DESC;
Extra:Using where; Using index; Using filesort
这里虽然用了索引(Using index),但因为SELECT * 需要所有列,MySQL先按索引找到主键,再回表拿全量数据,最后filesort排。这个filesort就要注意了——数据量大的话,灾难现场。
模式三:真正的filesort(双路排序)
Extra:Using filesort,没有Using index
这时MySQL分成两步:先按条件拿数据到sort buffer,用快排排好,再返回。这是最慢的情况,特别是sort buffer装不下的时候,会临时写磁盘。
怎么判断?
EXPLAIN FORMAT=JSON
SELECT * FROM order_items
WHERE product_id = 100
ORDER BY created_at DESC
LIMIT 10;
看sort_buffer_size和实际数据量。
Backward index scan:PostgreSQL玩剩下的,MySQL 8.0也有了
上面的输出里有个Backward index scan,很多人没见过。这是MySQL 8.0引入的优化。
当你ORDER BY created_at DESC的时候,如果created_at是升序索引,MySQL以前要全扫一遍再倒序。现在它可以倒着扫索引,性能提升明显。
但如果你同时有Using filesort,那这个优化就废了——filesort不支持预排序倒扫。
子查询的EXPLAIN:别被id骗了
EXPLAIN SELECT * FROM (
SELECT order_id, MAX(created_at) as last_time
FROM order_items
WHERE product_id > 0
GROUP BY order_id
) t
JOIN orders o ON t.order_id = o.id;
EXPLAIN输出里有id列,表示执行顺序。id越大越先执行。但这里有个坑:derived表(子查询)的id可能比外层大,不代表它真的先跑。
MySQL会对子查询进行物化(materialization),意思是把子查询结果存成临时表。物化之前,优化器会估算临时表的行数,这个估算值就藏在rows里。如果rows虚高,说明选错了执行计划。
解决方案:把子查询改成JOIN,或者加hint强制走索引。
SELECT * FROM order_items oi
INNER JOIN (
SELECT order_id, MAX(created_at) as last_time
FROM order_items
WHERE product_id > 0
GROUP BY order_id
) t ON t.order_id = oi.order_id;
压箱底工具:EXPLAIN ANALYZE
MySQL 8.0之后有个大杀器:EXPLAIN ANALYZE。它不仅显示计划,还会告诉你实际执行的时间和行数。
EXPLAIN ANALYZE
SELECT * FROM order_items
WHERE product_id = 100
ORDER BY created_at DESC
LIMIT 10;
输出类似:
-> Limit: 10 row(s) (actual time=0.021..0.035 rows=10 loops=1)
-> Backward index scan on order_items using idx_product_id
(actual time=0.018..0.030 rows=10 loops=1)
Filter: (product_id = 100)
Rows removed by filter: 1190
这里清清楚楚:扫了1200行,过滤掉1190行,才拿到10条。真正的性能瓶颈。
这就是EXPLAIN和EXPLAIN ANALYZE的区别——一个是预测,一个是实测。
实战优化流程
说一千道一万,给个可以直接抄的流程:
- 跑EXPLAIN ANALYZE,找actual time最大的节点
- 看rows和实际返回的比例,过滤性差就加条件或改索引
- 检查key_len,确认所有条件列都被索引覆盖
- 如果有filesort,看能不能改成覆盖索引
- 多表JOIN时,按id从小到大读,id相同从上往下读,检查驱动表是不是小表
最后
EXPLAIN是门手艺,不是玄学。那些能把慢查询快速定位出来的人,不是因为他们天赋异禀,而是因为他们真的把EXPLAIN的每个字段都读懂了。
下次老板问你为什么慢,你不需要急着加索引。给他看看EXPLAIN ANALYZE的输出,告诉他:实际扫描了1200行,实际耗时30毫秒,过滤掉了99%。这个数字,比任何口头解释都有说服力。
当然,如果老板继续追问,那就不是技术问题了。
完