Explain 执行计划解读

面试官问:"有一个慢查询,你知道怎么分析吗?"

小马说:"用 EXPLAIN。"

面试官:"好,那 EXPLAIN 输出里 type 列显示 ALL 是什么意思?"

小马:"...全表扫描?"

面试官追问:"那 key_len 怎么算?Using filesort 怎么优化?"

小马开始支支吾吾。

EXPLAIN 是 MySQL 性能优化的基石工具。会用 EXPLAIN 的人和不理解 EXPLAIN 输出的人,在排查慢查询时效率相差十倍以上。今天这篇,把 EXPLAIN 的每个字段全部讲透。

一、EXPLAIN 输出概览 🔴

1.1 基础输出

EXPLAIN SELECT * FROM orders WHERE status = 1;
+----+-------------+--------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+-------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | orders | NULL       | ref  | idx_status    | idx_status | 1   | const | 1000 |  100.00 | NULL  |
+----+-------------+--------+------------+------+---------------+-------+--------+-------+------+----------+-------+

1.2 各字段含义

字段含义重要程度
id查询编号,嵌套查询中标识子查询⭐⭐
select_type查询类型⭐⭐
table查询的表名⭐⭐⭐
partitions匹配的分区
type访问类型(核心字段)⭐⭐⭐⭐⭐
possible_keys可用的索引列表⭐⭐⭐
key实际使用的索引⭐⭐⭐⭐⭐
key_len索引使用的字节数⭐⭐⭐
ref与索引比较的列或常量⭐⭐
rows预计扫描的行数⭐⭐⭐
filtered过滤后剩余行数的百分比⭐⭐
Extra额外信息(重要)⭐⭐⭐⭐⭐

1.3 ❌ 错误示范

候选人原话:"type 列 ALL 就是全表扫描,很慢,要优化。"

问题诊断:ALL 不一定慢,要结合 rows 字段判断。如果表只有 100 行,ALL 可能比 ref 更快。优化不是消除 ALL,而是确保合适的场景用合适的访问方式。

候选人原话 2:"Using filesort 就是用了文件排序,很慢。"

问题诊断:filesort 不是"用磁盘文件排序",而是指在内存或磁盘中做额外排序。当数据量小(小于 sort_buffer_size)时,filesort 完全在内存中进行,不涉及磁盘。filesort 的问题是"额外排序步骤",不是"用了磁盘"。

【面试官心理】 EXPLAIN 是 MySQL 面试中出现频率最高的工具题。我通常会问两个层面:一是各字段的含义,二是根据 EXPLAIN 结果判断索引是否有效。很多候选人能背出字段,但不会根据输出做实际判断。

二、type 字段详解 🔴

2.1 type 从好到差排序

type 值含义说明
system表只有一行特殊常量表
const最多一行匹配(主键/唯一索引等值查询)极致优化,只查一次
eq_ref联表查询中,被驱动表用主键/唯一索引等值访问JOIN 场景的极致优化
ref非唯一索引等值访问普通索引查询
ref_or_nullref + 额外 NULL 值查找LIKE 'value%' 或 IS NULL
range索引范围扫描BETWEEN、IN、>、< 等
index全索引扫描需要扫描整个索引树
ALL全表扫描最差,需要优化

常见误区:index 和 ALL 都要扫描很多数据,但 index 扫描的是索引树而不是数据树。如果索引包含查询的所有字段,index 可能比 ALL 快(因为索引文件通常比数据文件小)。

2.2 常见 type 场景分析

-- const: 主键等值查询
SELECT * FROM orders WHERE id = 10086;                    -- type: const

-- ref: 普通索引等值查询
SELECT * FROM orders WHERE status = 1;                    -- type: ref

-- range: 范围查询
SELECT * FROM orders WHERE status > 1;                    -- type: range
SELECT * FROM orders WHERE status IN (1, 2, 3);           -- type: range

-- eq_ref: 联表查询,被驱动表用主键访问
SELECT * FROM orders o JOIN users u ON o.user_id = u.id;  -- u 表: type: eq_ref

-- index: 查询覆盖索引,但需要扫描整个索引
SELECT status FROM orders;                               -- type: index

-- ALL: 全表扫描
SELECT * FROM orders WHERE amount > 100;                  -- 无索引: type: ALL

2.3 优化目标

  • const / eq_ref:极致,大多数 OLTP 查询应达到
  • ref:良好,普通索引查询的正常水平
  • range:可接受,范围查询的正常表现
  • index:警告,检查是否可以覆盖索引优化
  • ALL:必须优化,尽量建立合适的索引

三、Extra 字段详解 🔴

3.1 常见 Extra 值

Extra 值含义优化建议
Using index覆盖索引,完全不需要回表最佳
Using index condition使用了 ICP良好
Using whereServer 层用 WHERE 过滤检查是否回表后过滤
Using filesort需要额外排序需要优化
Using temporary需要临时表需要优化
Using join bufferJOIN 时使用了缓存大表 JOIN 的警告
Backs index用了索引做回表需要优化
Not exists用了 ANTI JOIN 优化良好

3.2 Using filesort 详解

filesort 不是"用磁盘文件排序",而是"在拿到数据后需要额外排序"。

-- 需要 filesort
SELECT * FROM orders WHERE status = 1 ORDER BY created_at DESC;
-- 索引 idx(status, created_at) 可以覆盖此查询,不需 filesort
-- 如果没有这个索引,MySQL 会按 status 查出来,再按 created_at 排序

-- 不需要 filesort
SELECT * FROM orders WHERE status = 1 ORDER BY id DESC;
-- 如果主键索引包含在查询结果中,且索引有序

filesort 优化方法

  1. 建立合适的索引ORDER BY 的字段在索引中且满足最左前缀
  2. 减少排序数据量:加 WHERE 条件,缩小排序范围
  3. 增加 sort_buffer_size:增加内存排序区,减少磁盘排序

3.3 Using temporary 详解

-- 需要临时表
SELECT status, COUNT(*) FROM orders GROUP BY status;
-- MySQL 会创建临时表存储中间结果,再聚合

-- 不需要临时表
SELECT status, COUNT(*) FROM orders WHERE status = 1 GROUP BY status;
-- 缩小范围后可能直接在索引中完成

四、key_len 计算 🟡

4.1 key_len 的含义

key_len 表示索引使用的字节数,可以用来判断联合索引使用了多少列。

-- 表结构
-- id: BIGINT (8字节)
-- name: VARCHAR(32) 字符集 utf8mb4(每个字符 4 字节)
-- age: TINYINT (1 字节)

-- 索引: idx(id, name, age)

-- 查询: WHERE id = 1
-- key_len = 8  (只用了一列 id)

-- 查询: WHERE id = 1 AND name = 'Tom'
-- key_len = 8 + 32*4 = 136  (id + name,但 VARCHAR 需要存长度)
-- 精确计算: 8 + (32*4 + 2) = 142 (VARCHAR 头 2 字节存长度)

-- 查询: WHERE id = 1 AND name = 'Tom' AND age = 25
-- key_len = 8 + (32*4 + 2) + 1 = 143

4.2 通过 key_len 判断索引使用情况

-- 索引 idx(a, b, c)
-- EXPLAIN 显示 key_len = 4

-- 判断:用了几列?
-- a 是 INT (4字节) → 用了一列
-- b 是 VARCHAR(10) → 如果用了,key_len 至少 10*4+2 = 42

-- 结论:只用了一列 a

五、生产实战 🟡

5.1 慢查询排查流程

-- Step 1: 开启慢查询日志
SHOW VARIABLES LIKE 'slow_query%';
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过 1 秒记录

-- Step 2: 查看慢查询
SHOW GLOBAL STATUS LIKE 'Slow_queries';

-- Step 3: 分析执行计划
EXPLAIN SELECT ...

-- Step 4: 查看详细执行信息
EXPLAIN ANALYZE SELECT ...  -- MySQL 8.0+

5.2 联合查询的 EXPLAIN

EXPLAIN SELECT o.*, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 1;

-- 关键字段分析:
-- type: o 表应该是 ref 或 range,u 表应该是 eq_ref(主键访问)
-- key: o 表应该用 idx_status,u 表应该用 PRIMARY
-- rows: 评估扫描行数
💡

EXPLAIN ANALYZE(MySQL 8.0+)比 EXPLAIN 更进一步,它会实际执行查询并显示真实的耗时和行数,是更精确的性能分析工具。

【面试官心理】 问 EXPLAIN 的候选人里,能说出 type 和 Extra 的常见值的是大多数。能说出 key_len 计算方法的就不多了。能根据一个 EXPLAIN 输出判断索引设计是否合理、SQL 是否需要优化的,是真正有实战经验的候选人。


级别考察重点期望回答
P5基础字段type、key、rows、Extra 常见值含义
P6深度理解key_len 计算、filesort 优化、ICP 识别
P7综合分析完整排查流程、多表 JOIN 分析、性能调优方案