写SQL谁都会,但写出高效的SQL就需要点本事了。EXPLAIN 是 MySQL 提供的 SQL 分析神器,学会用它,优化速度能提升好几个量级。

什么是执行计划?

当你执行一条 SELECT 语句时,MySQL 优化器会决定用哪种查询方式——走索引还是全表扫描?关联顺序怎么选?这些决策的依据就是执行计划

EXPLAIN SELECT * FROM user WHERE id = 1;

这行命令会告诉你:这条查询有没有走索引、用了哪个索引、扫描了多少行等等。

EXPLAIN 输出详解

EXPLAIN 的输出有十几列,最重要的有这几个:

列名 说明
id 查询序号,越大越先执行
select_type 查询类型
table 查询的表
type 访问类型(最重要)
possible_keys 可能用到的索引
key 实际用到的索引
key_len 索引长度
rows 预计扫描行数
Extra 额外信息

type:访问类型(最重要!)

这一列直接反映查询效率,从最优到最差排序:

type 值 含义 说明
const 常量查询 用主键或唯一索引,只扫描1行
eq_ref 唯一索引扫描 关联时用主键或唯一索引
ref 非唯一索引扫描 用普通索引,扫描多行
range 范围查询 索引范围扫描(between、>、<)
index 全索引扫描 遍历整个索引树
ALL 全表扫描 最糟糕,需要遍历全部数据

实战例子

-- const:最优
EXPLAIN SELECT * FROM user WHERE id = 1;  -- 主键查询

-- ref:较好
EXPLAIN SELECT * FROM user WHERE age = 25;  -- 普通索引

-- range:还可以
EXPLAIN SELECT * FROM user WHERE id > 100 AND id < 200;  -- 范围查询

-- index:一般
EXPLAIN SELECT id FROM user;  -- 只查索引列

-- ALL:最差!
EXPLAIN SELECT * FROM user WHERE name = 'Tom';  -- 没有索引

key:实际用的索引

EXPLAIN SELECT * FROM user WHERE age = 25 AND name = 'Tom';

输出:

| table | possible_keys | key      |
|-------|--------------|----------|
| user  | idx_age_name | idx_age  |
  • possible_keys:可能用到的索引(优化器觉得能用哪些)
    • key:实际用到的索引(优化器决定用哪个)
      有时候 possible_keys 有值,但 key 是 NULL,说明索引失效了

rows:预计扫描行数

这一列估计要扫描多少行数据。数值越小越好

EXPLAIN SELECT * FROM user WHERE age > 20;

如果 rows = 1000000,说明要扫100万行——这肯定有问题,得加索引或者优化SQL。

Extra:额外信息

这个列包含很多关键信息,常见的有:

Using filesort(⚠️ 危险!)

需要额外排序,filesort 是 MySQL 的额外排序算法,效率低。

EXPLAIN SELECT * FROM user ORDER BY created_at;

解决方案:在排序字段上建索引。

Using index(✅ 好!)

直接用索引就返回数据,不需要回表。

EXPLAIN SELECT id, name FROM user WHERE id > 10;

Using where(普通)

使用了 WHERE 条件过滤。

Using index condition(⚠️ 注意)

用了索引下推(Index Condition Pushdown),比回表好,但不是最优。

Using temporary(⚠️ 危险!)

需要创建临时表,效率低。

EXPLAIN SELECT name, COUNT(*) FROM user GROUP BY name;

优化思路:考虑用索引覆盖,或者改写SQL。

Using index & Using where

同时出现说明用索引完成了过滤和返回,是比较理想的状态。

关联查询的执行计划

关联查询(JOIN)的执行计划会显示多条记录,因为涉及多个表:

EXPLAIN 
SELECT u.*, o.* 
FROM user u 
INNER JOIN order o ON u.id = o.user_id 
WHERE u.age > 20;

id:执行顺序

  • id 相同,从上到下执行
    • id 不同,id 大的先执行
    • id 为 NULL,先执行临时表

select_type:查询类型

说明
SIMPLE 简单查询,不含子查询和 UNION
PRIMARY 主查询,最外层
SUBQUERY 子查询
DERIVED 派生表(FROM 子查询)
UNION UNION 后的查询
UNION RESULT UNION 结果

实战:分析一条慢SQL

原始SQL

SELECT * 
FROM order 
WHERE user_id = 100 
AND status = 'pending' 
ORDER BY created_at DESC 
LIMIT 10;

EXPLAIN 分析

EXPLAIN 
SELECT * 
FROM order 
WHERE user_id = 100 
AND status = 'pending' 
ORDER BY created_at DESC 
LIMIT 10;

假设输出

| id | select_type | table | type | possible_keys | key     | rows | Extra                |
|----|-------------|-------|------|--------------|---------|------|---------------------|
| 1  | SIMPLE      | order | ref  | idx_user_id  | idx_user_id | 5000 | Using where; Using filesort |

问题诊断

  1. type = ref:还行,用了索引
    1. rows = 5000:要扫5000行,有点多
    1. Using filesort:需要额外排序,效率低

优化方案

建一个联合索引 (user_id, status, created_at)

CREATE INDEX idx_user_status_created ON order(user_id, status, created_at);

优化后

| id | select_type | table | type | possible_keys | key                      | rows | Extra |
|----|-------------|-------|------|--------------|--------------------------|------|-------|
| 1  | SIMPLE      | order | ref  | idx_user_status_created | idx_user_status_created | 10   | Using index |
  • rows 从 5000 降到 10
    • 用到索引覆盖,不需要 filesort
    • Extra 显示 Using index,完美!

小技巧

1. 查看索引使用情况

SHOW INDEX FROM user;

2. 查看表结构

SHOW CREATE TABLE user;

3. 强制使用索引

SELECT * FROM user FORCE INDEX (idx_age) WHERE age = 25;

⚠️ 谨慎使用,强制索引可能导致更差的执行计划。

4. 查看详细执行计划(MySQL 8.0+)

EXPLAIN ANALYZE SELECT * FROM user WHERE id = 1;

会显示实际执行时间和开销,比 EXPLAIN 更精确。

总结

指标 理想值 说明
type const/eq_ref/ref 避免 ALL
key 非 NULL 必须用索引
rows 越小越好 扫描行数
Extra Using index 避免 Using filesort/temporary

EXPLAIN 是 SQL 优化的第一步,也是最关键的一步。拿到一条慢SQL,先 EXPLAIN 看看执行计划,问题基本就清楚了一半。


相关阅读:

  • [MySQL 索引底层 B+ 树原理]
    • [MySQL 慢查询优化实战]
    • [MySQL 索引失效的七种情况]
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐