前言
写 SQL 很容易,写出高性能的 SQL 却很难。
当你发现接口响应慢了,第一反应肯定是:“这个 SQL 到底走了索引没?”
别去猜,MySQL 提供了最强大的诊断工具 —— EXPLAIN 命令。
它能模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 的。
今天我们就来破解 EXPLAIN 输出的那张“天书”表格,重点掌握 3 个最核心的指标。
1. 怎么用?
用法超级简单,就在你的 SELECT 语句前面加上 EXPLAIN 即可。
SQL
EXPLAIN SELECT * FROM user WHERE id = 1;
执行后,你会看到类似下面的一张表:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| 1 | SIMPLE | user | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL |
这就是执行计划。接下来我们逐一拆解重点。
2. 核心指标一:type (访问类型) —— 性能的风向标
这是最重要的一列!它告诉我们 MySQL 是怎么查找数据的:是根据索引直接定位,还是苦逼地全表扫描?
性能从好到坏依次是:
- system / const (最快)
- 含义: 只有一行匹配。
- 场景: 根据 主键 或 唯一索引 查询。
- 例子:
WHERE id = 1
- eq_ref
- 含义: 使用主键或唯一索引进行的关联查询。
- 场景: 多表连接时,连接条件是主键。
- ref (常见/合格)
- 含义: 使用了普通索引(非唯一)。
- 场景:
WHERE name = 'Tom'(name 字段有索引)。
- range (必须达到)
- 含义: 索引范围扫描。
- 场景:
BETWEEN,>,<,IN等查询。 - 例子:
WHERE age > 18
- index (较差)
- 含义: 全索引扫描。虽然没扫描数据行,但把整个索引树扫了一遍。
- 场景:
SELECT id FROM user(id是索引,但没加条件)。
- ALL (最差/必须优化)
- 含义: 全表扫描 (Full Table Scan)。
- 场景: 没建索引,或者索引失效(如
LIKE '%张')。 - 后果: 如果表有百万数据,这波操作就是灾难。
优化目标: 至少要达到 range 级别,最好能达到 ref。
3. 核心指标二:key (实际使用的索引)
这一列告诉你真相:MySQL 到底有没有用你建的索引?
- possible_keys:显示可能应用在这张表上的索引(理论值)。
- key:实际使用的索引(实际值)。
- 如果为 NULL,说明没走索引,快去检查
WHERE条件! - 如果
possible_keys有值但key为 NULL,说明 MySQL 觉得走索引比全表扫描还慢(通常发生在数据量很小,或者数据分布极不均匀时)。
4. 核心指标三:Extra (额外信息) —— 藏着魔鬼的细节
这一列包含了很多额外信息,注意看有没有出现以下**“红色警报”**:
- Using filesort (严重)
- 含义: MySQL 无法利用索引完成排序,必须在内存或磁盘中进行额外的排序操作。
- 后果: CPU 消耗巨大。
- 解决: 给
ORDER BY的字段加上索引。
- Using temporary (严重)
- 含义: 使用了临时表来保存中间结果。
- 场景: 常见于
GROUP BY或DISTINCT。 - 解决: 优化索引,避免创建临时表。
- Using index (好事)
- 含义: 覆盖索引 (Covering Index)。
- 解释: 查询的列全都在索引树里就能找到,不需要回表去查数据行。这是查询性能的最高境界。
5. 核心指标四:rows (扫描行数)
- 含义: MySQL 预估为了找到你想要的数据,需要扫描多少行。
- 解读: 这个数字越小越好。
- 如果你查 1 条数据,rows 却是 10000,说明索引建立得很糟糕,或者区分度太低。
实战案例:读懂一个慢查询
假设 SQL 是:
SQL
SELECT * FROM order WHERE user_id = 100 ORDER BY create_time;
EXPLAIN 结果 A(优化前):
type: ALLkey: NULLExtra: Using filesort- 分析: 全表扫描 + 文件排序,双重打击,必须死。
优化方案: 建立联合索引 idx_user_time (user_id, create_time)。
EXPLAIN 结果 B(优化后):
type: ref (根据 user_id 查找)key: idx_user_timeExtra: NULL (利用索引原本的顺序,避免了 filesort)- 分析: 完美!
总结
以后看 EXPLAIN,先看这三点:
- type 是不是
ALL?(是就得改) - key 是不是
NULL?(是就加索引) - Extra 有没有
filesort/temporary?(有就优化索引顺序)