EXPLAIN 解读
约 1131 字大约 4 分钟
布欧-Lewyon
2026-05-15
首页 › MySQL › 索引与性能(在新窗口打开) › EXPLAIN 解读
EXPLAIN 是 MySQL 最重要的查询分析工具,它展示优化器对 SQL 语句的执行计划。学会解读 EXPLAIN 的输出,是 SQL 优化和索引设计的必备技能。
基本用法
EXPLAIN SELECT e.name, d.name AS dept_name
FROM employee e
JOIN department d ON e.dept_id = d.id
WHERE e.salary > 10000;输出列说明:
| 列名 | 含义 |
|---|---|
id | SELECT 标识符,越大优先级越高 |
select_type | 查询类型 |
table | 表名 |
type | 访问类型(重点) |
possible_keys | 可能使用的索引 |
key | 实际使用的索引 |
key_len | 索引使用的字节数 |
ref | 与索引比较的列或常量 |
rows | 估算扫描行数 |
Extra | 额外信息(重点) |
type —— 访问类型(性能从好到差)
-- const:主键或唯一索引的等值查询,最多返回一行
EXPLAIN SELECT * FROM employee WHERE id = 1;
-- type: const, Extra: NULL
-- eq_ref:JOIN 时使用主键或唯一索引的等值查找
EXPLAIN SELECT * FROM employee e JOIN department d ON e.dept_id = d.id;
-- d 表为 eq_ref(假设 d.id 是主键)
-- ref:普通索引的等值查找
EXPLAIN SELECT * FROM employee WHERE dept_id = 1;
-- type: ref(假设 dept_id 有索引)
-- range:索引范围扫描
EXPLAIN SELECT * FROM employee WHERE salary BETWEEN 10000 AND 20000;
-- type: range(salary 有索引)
-- index:扫描整个索引树(比 ALL 好一点,但仍很差)
EXPLAIN SELECT COUNT(*) FROM employee;
-- type: index(扫描索引而非数据行)
-- ALL:全表扫描(最差)
EXPLAIN SELECT * FROM employee WHERE name LIKE '%七';
-- type: ALL| type | 说明 | 建议 |
|---|---|---|
const | 最多 1 行 | 最优 |
eq_ref | JOIN 主键/唯一键 | 极优 |
ref | 普通索引等值 | 良 |
range | 索引范围 | 可接受 |
index | 索引全扫描 | 需优化 |
ALL | 全表扫描 | 危险 |
目标是让
type至少达到range,最好到ref或以上。
Extra 的常见值
-- Using index:覆盖索引(不回表)
EXPLAIN SELECT dept_id, salary FROM employee WHERE dept_id = 1;
-- Extra: Using index
-- Using where:在存储引擎返回后,Server 层做额外过滤
EXPLAIN SELECT * FROM employee WHERE salary > 10000 AND name LIKE '张%';
-- Using filesort:需要额外排序(无法用索引排序)
EXPLAIN SELECT * FROM employee ORDER BY salary DESC;
-- Extra: Using filesort(如果 salary 没索引)
-- Using temporary:使用了临时表(GROUP BY 或 DISTINCT)
EXPLAIN SELECT dept_id, COUNT(*) FROM employee GROUP BY dept_id;
-- Extra: Using temporary(如果 dept_id 没索引)
-- Using index condition:索引下推(ICP,5.6+)
EXPLAIN SELECT * FROM employee WHERE dept_id = 1 AND salary LIKE '15%';
-- Extra: Using index condition
-- Using where; Using index:在覆盖索引上做条件过滤目标:尽量避免 Using filesort 和 Using temporary;出现 Using index 是好现象。
key_len —— 索引使用长度
key_len 表示 MySQL 在索引中使用了多少字节。值越大,说明使用的索引部分越多。
-- 复合索引 (dept_id, salary)
-- dept_id 是 INT (4 字节,NOT NULL)
-- salary 是 DECIMAL(10,2) (5 字节)
EXPLAIN SELECT * FROM employee WHERE dept_id = 1;
-- key_len: 4(只用了 dept_id 部分)
EXPLAIN SELECT * FROM employee WHERE dept_id = 1 AND salary = 15000;
-- key_len: 9(用了 dept_id + salary 全部)常见类型字节数:
| 类型 | 长度(字节) |
|---|---|
| TINYINT | 1 |
| INT | 4 |
| BIGINT | 8 |
| VARCHAR(100) | 100 × 字符集字节数 + 2 |
| DATETIME (5.6+) | 5 |
| TIMESTAMP | 4 |
VARCHAR的长度计算公式:字符数 × 字符集最大字节 + 2(如VARCHAR(100) utf8mb4为 100×4+2=402)。
rows —— 估算扫描行数
rows 是优化器估算的需要扫描的行数。比值 rows / 表总行数 越小越好。如果比例超过 20~30%,即使有索引,优化器也可能选择全表扫描。
EXPLAIN ANALYZE(MySQL 8.0.18+)
可以查看实际的执行时间和行数:
EXPLAIN ANALYZE SELECT e.name, d.name AS dept_name
FROM employee e
JOIN department d ON e.dept_id = d.id
WHERE e.salary > 10000;输出格式:
-> Nested loop inner join (cost=... rows=...) (actual time=... rows=... loops=...)
-> Filter: (e.salary > 10000) (cost=... rows=...) (actual time=... rows=...)
-> Table scan on e (cost=... rows=...)
-> Single-row index lookup on d using PRIMARY (id=e.dept_id) (cost=...)actual time 是实际执行时间,比 rows 估算值更准确。
一个完整的优化案例
-- 慢查询
SELECT * FROM order_ WHERE YEAR(create_time) = 2026 ORDER BY create_time DESC;
-- EXPLAIN 结果
-- type: ALL(全表扫描)
-- Extra: Using where; Using filesort
-- 优化:把函数调用改为范围查询,并加复合索引
CREATE INDEX idx_create_time ON order_(create_time);
-- 改写
SELECT * FROM order_
WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'
ORDER BY create_time DESC;
-- EXPLAIN 结果
-- type: range
-- Extra: Using index condition
-- rows 大幅减少小结
EXPLAIN是分析查询的核心工具,重点关注type、key、rows、Extra。type从好到差:const→eq_ref→ref→range→index→ALL。Extra中Using filesort和Using temporary是优化信号,Using index是好消息。key_len越大表示索引利用率越高。EXPLAIN ANALYZE(8.0.18+)提供实际执行时间,比估算更可靠。
