使用EXPLAIN 分析慢 SQL ,看哪些东西


使用EXPLAIN 分析某条慢 SQL 的执行计划,应该看哪些内容,从而去分析、解决慢sql

EXPLAIN 是分析慢 SQL 的"X 光机"。但很多人执行完 EXPLAIN 后面对十几个字段不知道看什么——其实只需要盯住 4 个核心字段,按固定顺序排查,90% 的慢 SQL 都能定位到原因。

下面按排查优先级来"看 EXPLAIN 的标准动作"。


一、先看什么:四个核心字段(按优先级)

执行 EXPLAIN SELECT ... 后,输出大概长这样:

+----+-------------+----------+-------+---------------+---------+---------+------+--------+-------------+
| id | select_type | table    | type  | possible_keys | key     | key_len | ref  | rows   | Extra       |
+----+-------------+----------+-------+---------------+---------+---------+------+--------+-------------+
|  1 | SIMPLE      | sys_user | range | PRIMARY,idx_n | PRIMARY | 8       | NULL |     20 | Using where |
+----+-------------+----------+-------+---------------+---------+---------+------+--------+-------------+

排查顺序:type → key → rows → Extra,这四个字段能告诉你 90% 的信息。


二、字段一:type(访问类型)—— 最先看,决定生死

type 表示 MySQL 用什么方式找到数据的,从快到慢排:

const > eq_ref > ref > range > index > ALL
type含义性能是否需优化
const主键/唯一索引等值查询,最多返回 1 行⭐⭐⭐⭐⭐不需要
eq_refJOIN 时主键/唯一索引关联⭐⭐⭐⭐⭐不需要
ref非唯一索引等值查询⭐⭐⭐⭐通常 OK
range索引范围扫描(>, <, BETWEEN, IN)⭐⭐⭐⭐游标分页的理想状态
index全索引扫描(遍历整个索引树)⭐⭐需优化
ALL全表扫描(遍历整张表)⭐必须优化

怎么判断

-- ❌ type = ALL → 全表扫描,千万级表直接卡死
EXPLAIN SELECT * FROM sys_user WHERE name = '张三';
-- 原因:name 字段没建索引

-- ✅ type = ref → 走非唯一索引
EXPLAIN SELECT * FROM sys_user WHERE phone = '13800138000';
-- 原因:phone 有普通索引

-- ✅ type = range → 索引范围扫描(你的游标分页就是这种)
EXPLAIN SELECT * FROM sys_user WHERE id > 100000 ORDER BY id LIMIT 20;
-- 原因:id 是主键,用了 > 条件

口诀:看到 ALL 就一定要优化,看到 index 也要想想能不能改成 range 或 ref。


三、字段二:key(实际使用的索引)

key 告诉你 MySQL 最终选了哪个索引。

情况含义对策
key = PRIMARY走了主键索引√ 最好情况
key = 你建的某个索引走了预期索引√ 正常
key = NULL没走任何索引× 必须查为什么
key 不是你期望的索引优化器选错了用 FORCE INDEX 或分析统计信息

为什么有索引但 key = NULL?

常见原因:

-- 1. 对索引列做了函数操作
WHERE DATE(create_time) = '2024-01-01'   -- × 索引失效
WHERE create_time >= '2024-01-01 00:00:00' AND create_time < '2024-01-02 00:00:00'  -- √

-- 2. 隐式类型转换
WHERE phone = 13800138000    -- × phone 是 varchar,传入数字导致全表扫描
WHERE phone = '13800138000'  -- √

-- 3. LIKE 以 % 开头
WHERE name LIKE '%张%'       -- × 索引失效,全表扫描
WHERE name LIKE '张%'        -- √ 可以用索引

-- 4. 优化器认为全表扫描更快(数据量太小或索引区分度太低)
-- 解决:ANALYZE TABLE sys_user; 更新统计信息

四、字段三:rows(估算扫描行数)

rows 是优化器估算的需要扫描的行数(不是精确值)。

怎么看

rows = 1          → 几乎完美
rows < 1000       → 正常
rows < 10000      → 还行,但需注意
rows > 100000     → 有问题,需要优化
rows = 全表行数   → 全表扫描,灾难

关键对比

-- 游标分页
EXPLAIN SELECT * FROM sys_user WHERE id > 100000 ORDER BY id LIMIT 20;
-- rows ≈ 20 → √ 只扫 20 行

-- 深分页
EXPLAIN SELECT * FROM sys_user ORDER BY id LIMIT 100000, 20;
-- rows ≈ 100020 → × 扫了 10 万行

-- 全表扫描
EXPLAIN SELECT * FROM sys_user WHERE name LIKE '%张%';
-- rows ≈ 全表行数(比如 5000000)→ × 扫了 500 万行

口诀:rows 和你预期返回的行数差距越大,说明索引越没用上。


五、字段四:Extra(额外信息)—— 信息量最大

Extra 是最容易暴露问题的字段,几个关键值:

好消息

Extra 值含义
Using index覆盖索引,不需要回表,最快
Using index condition索引下推 (ICP),在存储引擎层就过滤了数据,减少回表

坏消息(需要优化)

Extra 值含义怎么优化
Using filesort需要额外排序,没用上索引的有序性建联合索引让 ORDER BY 走索引
Using temporary用了临时表(GROUP BY / DISTINCT / UNION)加索引或简化 SQL
Using where服务层做了额外过滤(索引没完全过滤掉)看是否能在索引层面就过滤
Select tables optimized away优化器直接从索引返回结果,不需要查表✅ 最好情况

六、实战:用 EXPLAIN 分析一条真实慢 SQL

场景:后台用户列表分页

-- 慢 SQL(执行 3 秒)
SELECT id, name, phone, status, create_time 
FROM sys_user 
WHERE name LIKE '%张%' 
  AND status = 1 
ORDER BY create_time DESC 
LIMIT 100000, 20;

Step 1:EXPLAIN 看结果

+----+-------------+----------+------+---------------+------+---------+------+---------+-----------------------------+
| id | select_type | table    | type | possible_keys | key  | rows    | Extra                              |
+----+-------------+----------+------+---------------+------+---------+------+---------+-----------------------------+
|  1 | SIMPLE      | sys_user | ALL  | idx_status    | NULL | 5000000 | Using where; Using filesort        |
+----+-------------+----------+------+---------------+------+---------+------+---------+-----------------------------+

Step 2:逐项分析

字段值问题
typeALL❌ 全表扫描 500 万行
keyNULL❌ 有 idx_status 但没走(因为 name LIKE '%张%' 让优化器觉得走索引不如全表扫)
rows5000000❌ 扫了全表
ExtraUsing where; Using filesort❌ 没走索引过滤 + 需要额外排序

Step 3:优化方案

方案 A:业务层面(推荐)

  • 禁止 LIKE '%xx%' 前导通配符,改成 LIKE '张%'(能走索引)
  • 深分页改成游标分页(前面讲过的方案)

方案 B:索引层面

-- 建联合索引,让 WHERE + ORDER BY 都能走索引
CREATE INDEX idx_status_create_time ON sys_user(status, create_time DESC);
-- 这样 status = 1 走索引,create_time 有序,避免 filesort

方案 C:搜索引擎

  • 如果必须支持 %关键词% 模糊搜索,把数据同步到 Elasticsearch

七、联合索引的最左前缀原则(必知)

很多人建了联合索引但 EXPLAIN 显示没走,是因为违反了最左前缀原则:

-- 建了联合索引:idx_a_b_c (a, b, c)
SQL 条件能走索引?说明
WHERE a = 1✅用了最左列
WHERE a = 1 AND b = 2✅用了 a + b
WHERE a = 1 AND b = 2 AND c = 3✅用了 a + b + c
WHERE b = 2❌没用最左列 a
WHERE a = 1 AND c = 3⚠️只用了 a,c 用不上
WHERE a > 1 AND b = 2⚠️a 走范围后,b 的有序性被破坏

口诀:联合索引像电话号,必须从最左边开始拨。


八、标准排查流程(记住这个顺序)

1. EXPLAIN 看 type
   ├── ALL → 全表扫描,检查 WHERE 条件有没有索引
   ├── index → 全索引扫描,看能不能改成 range
   └── range/ref → 继续看下面

2. 看 key 是不是预期索引
   ├── NULL → 索引失效(函数/类型转换/LIKE 前缀%)
   └── 不是预期的 → 优化器选错了,ANALYZE TABLE 或 FORCE INDEX

3. 看 rows 是不是远大于实际返回行数
   └── 是 → 索引区分度低,考虑组合索引

4. 看 Extra
   ├── Using filesort → ORDER BY 没走索引,建联合索引
   ├── Using temporary → GROUP BY 没走索引
   └── Using index → 完美,覆盖索引

5. 如果以上都 OK 但还是慢 → 看是不是锁等待、IO 瓶颈、网络问题

九、一个实用技巧:EXPLAIN FORMAT=JSON

如果你用的是 MySQL 5.7+,可以用 JSON 格式看更详细的信息:

EXPLAIN FORMAT=JSON 
SELECT * FROM sys_user WHERE id > 100000 ORDER BY id LIMIT 20;

JSON 输出里能看到:

  • query_cost:查询成本(数字越小越好)
  • used_columns:用到了哪些列
  • attached_condition:实际过滤条件

十、总结口诀

看 EXPLAIN,记住四句话:

  1. type 是 ALL,必须得改
  2. key 是 NULL,索引白建
  3. rows 特别大,扫描太多
  4. Extra 有 filesort,排序没走索引

JAVA-技能点
知识点
Mysql