MySQL EXPLAIN 执行计划详解:从入门到精通的 SQL 性能分析指南

深入解析 MySQL EXPLAIN 执行计划的 12 个关键字段,掌握 type 访问类型、key 索引选择、Extra 额外信息的含义,学会通过执行计划分析和优化 SQL 查询性能。

Bruce

MySQLEXPLAINSQL优化性能调优索引数据库

MySQL

1029 Words

2019-09-06 12:10 +0000


MySQL EXPLAIN 执行计划分析

EXPLAIN 是 MySQL 中最重要的性能分析工具之一。当你的 SQL 查询变慢时,第一件事就应该用 EXPLAIN 看看执行计划。它能告诉你 MySQL 是如何执行查询的,包括使用了哪些索引、扫描了多少行、采用了什么连接方式等关键信息。

本文将详细介绍 EXPLAIN 输出的每个字段含义,帮助你快速定位 SQL 性能问题。

一、EXPLAIN 基本用法

使用方法很简单,在 SQL 语句前加上 EXPLAIN 关键字即可:

EXPLAIN SELECT * FROM users WHERE id = 1;

MySQL 8.0+ 还支持不同的输出格式:

-- 传统表格格式(默认)
EXPLAIN SELECT * FROM users;

-- JSON 格式,信息更详细
EXPLAIN FORMAT=JSON SELECT * FROM users;

-- 树形格式,展示执行顺序
EXPLAIN FORMAT=TREE SELECT * FROM users;

-- 实际执行并显示运行时统计(MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT * FROM users;

提示EXPLAIN ANALYZE 会真正执行查询,所以对于修改数据的语句要谨慎使用。

二、EXPLAIN 输出字段详解

EXPLAIN 的输出包含 12 个字段,每个字段都提供了重要的执行信息:

字段说明
idSELECT 查询的序列号
select_typeSELECT 的类型
table查询的表名
partitions匹配的分区
type访问类型(重要)
possible_keys可能使用的索引
key实际使用的索引
key_len使用的索引长度
ref与索引比较的列
rows预估扫描的行数
filtered按条件过滤的行百分比
Extra额外信息(重要)

下面逐一详解每个字段。

2.1 id - 查询序列号

id 表示 SELECT 语句的执行顺序:

  • id 相同:从上往下依次执行
  • id 不同:id 值越大优先级越高,越先执行
  • id 为 NULL:表示这是一个结果集,不需要用来查询
-- 示例:子查询
EXPLAIN
SELECT * FROM orders
WHERE user_id IN (SELECT id FROM users WHERE status = 1);

2.2 select_type - 查询类型

select_type 表示查询的类型,常见值如下:

类型说明
SIMPLE简单查询,不包含子查询或 UNION
PRIMARY最外层的 SELECT
SUBQUERYSELECT 或 WHERE 中的子查询
DERIVEDFROM 子句中的子查询(派生表)
UNIONUNION 中第二个及之后的 SELECT
UNION RESULTUNION 的结果集
DEPENDENT SUBQUERY依赖外部查询的子查询
DEPENDENT UNION依赖外部查询的 UNION
MATERIALIZED物化子查询
-- SIMPLE 示例
EXPLAIN SELECT * FROM users WHERE id = 1;

-- SUBQUERY 示例
EXPLAIN SELECT * FROM orders
WHERE user_id = (SELECT id FROM users WHERE name = 'test');

-- DERIVED 示例
EXPLAIN SELECT * FROM (SELECT * FROM users WHERE status = 1) AS t;

2.3 table - 表名

table 显示这一行数据是关于哪张表的。特殊值:

  • <derived_N_>:id 为 N 的派生表
  • <union_M_,_N_>:id 为 M 和 N 的 UNION 结果
  • <subquery_N_>:id 为 N 的物化子查询

2.4 partitions - 分区

如果表进行了分区,这里会显示查询涉及的分区。非分区表显示 NULL。

2.5 type - 访问类型(重要)

type 是 EXPLAIN 中最重要的字段之一,它表示 MySQL 如何查找表中的行。性能从好到差排序

system > const > eq_ref > ref > fulltext > ref_or_null >
index_merge > unique_subquery > index_subquery > range > index > ALL

type 类型详解

type性能说明触发条件
system最好表只有一行const 的特例
const极好通过索引一次找到,最多一行PRIMARY KEY 或 UNIQUE 索引等值查询
eq_ref很好唯一索引扫描,每个索引键只有一条记录多表 JOIN 时使用 PRIMARY KEY 或 UNIQUE NOT NULL 索引
ref非唯一索引扫描,可能返回多行使用普通索引或唯一索引的前缀
fulltext一般全文索引使用 FULLTEXT 索引
ref_or_null一般类似 ref,但额外搜索 NULL 值WHERE col = value OR col IS NULL
index_merge一般索引合并优化同时使用多个索引
range一般索引范围扫描BETWEEN、<、>、IN 等范围条件
index全索引扫描遍历整个索引树
ALL最差全表扫描没有可用索引

实际示例

-- const:主键等值查询
EXPLAIN SELECT * FROM users WHERE id = 1;
-- type: const

-- ref:普通索引查询
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';
-- type: ref(假设 email 有普通索引)

-- range:范围查询
EXPLAIN SELECT * FROM orders WHERE created_at > '2024-01-01';
-- type: range(假设 created_at 有索引)

-- ALL:全表扫描
EXPLAIN SELECT * FROM users WHERE name LIKE '%test%';
-- type: ALL(前缀模糊查询无法使用索引)

优化目标:让查询至少达到 range 级别,最好能达到 ref 或更好。如果出现 ALL,通常需要考虑添加索引。

2.6 possible_keys - 可能使用的索引

显示可能应用到这张表的索引。这些索引不一定会被实际使用,只是 MySQL 认为可能有用的候选。

2.7 key - 实际使用的索引

显示 MySQL 实际选择使用的索引。如果为 NULL,说明没有使用索引。

有时候 key 列显示的索引不在 possible_keys 中,这是因为 MySQL 发现了覆盖索引(Covering Index),可以直接从索引获取所有需要的数据。

2.8 key_len - 索引长度

表示使用的索引字节数。通过这个值可以判断是否充分利用了复合索引:

-- 假设有复合索引 idx_name_age (name, age)
-- name VARCHAR(50), age INT

EXPLAIN SELECT * FROM users WHERE name = 'test';
-- key_len: 152 (50*3+2,VARCHAR 使用 3 倍字符长度 + 2)

EXPLAIN SELECT * FROM users WHERE name = 'test' AND age = 25;
-- key_len: 157 (152+4+1,额外使用了 INT 的 4 字节,NULL 标志 1 字节)

key_len 计算规则

  • CHAR(n):n × 字符集字节数
  • VARCHAR(n):n × 字符集字节数 + 2
  • INT:4 字节
  • BIGINT:8 字节
  • DATE:3 字节
  • DATETIME:8 字节
  • 允许 NULL 的列额外 +1 字节

2.9 ref - 参考列

显示哪些列或常量被用于与 key 列中的索引进行比较。常见值:

  • const:常量比较
  • 字段名:与某个字段比较
  • func:使用了函数

2.10 rows - 预估行数

MySQL 估算需要检查的行数。这个数字是估计值,不是精确值。

rows 值越小越好。如果 rows 很大,说明查询效率可能不高。

2.11 filtered - 过滤比例

表示存储引擎返回的数据经过 WHERE 条件过滤后,剩余数据的百分比。

rows × filtered% = 最终返回的行数估计

-- 如果 rows = 1000, filtered = 10.00
-- 预计最终返回约 100 行

2.12 Extra - 额外信息(重要)

Extra 列包含了很多重要的执行细节:

需要关注的值(可能影响性能)

说明建议
Using filesort需要额外排序操作考虑添加合适的索引来避免排序
Using temporary使用了临时表通常出现在 GROUP BY、DISTINCT、ORDER BY 中
Using where存储引擎检索后再由 Server 层过滤正常现象,但大量过滤可能需要优化

较好的值

说明
Using index覆盖索引,无需回表
Using index condition索引下推(ICP),在存储引擎层过滤
Using index for group-by使用索引优化 GROUP BY
Using index for skip scan索引跳跃扫描(MySQL 8.0+)

其他常见值

说明
Impossible WHEREWHERE 条件永远为 false
Select tables optimized away优化阶段已确定结果(如 MIN/MAX)
No matching min/max row没有满足条件的行
Distinct找到第一个匹配后停止搜索
Using join buffer使用连接缓冲区(Block Nested Loop 或 Hash Join)
Using MRR多范围读取优化

Extra 示例

-- Using index(覆盖索引)
EXPLAIN SELECT id, name FROM users WHERE name = 'test';
-- 假设有索引 idx_name(name),且只查询 id 和 name

-- Using filesort
EXPLAIN SELECT * FROM orders ORDER BY amount;
-- 假设 amount 列没有索引

-- Using temporary; Using filesort
EXPLAIN SELECT department, COUNT(*) FROM employees
GROUP BY department ORDER BY COUNT(*) DESC;

三、Visual Explain 可视化分析

MySQL Workbench 提供了 Visual Explain 功能,可以将执行计划可视化展示。

MySQL Workbench Visual Explain 示例

颜色含义

颜色访问类型成本级别
蓝色system、const很低(最优)
绿色eq_ref、ref、ref_or_null、index_merge
黄色fulltext
橙色unique_subquery、index_subquery、range中等
红色index、ALL高(需要优化)

执行顺序

Visual Explain 的执行顺序是 从下到上,从左到右

四、EXPLAIN 实战案例

案例 1:优化全表扫描

-- 问题 SQL
EXPLAIN SELECT * FROM orders WHERE status = 'pending';
-- type: ALL, rows: 100000

-- 添加索引后
ALTER TABLE orders ADD INDEX idx_status(status);

EXPLAIN SELECT * FROM orders WHERE status = 'pending';
-- type: ref, rows: 500

案例 2:优化 filesort

-- 问题 SQL
EXPLAIN SELECT * FROM orders WHERE user_id = 100 ORDER BY created_at;
-- Extra: Using filesort

-- 创建复合索引
ALTER TABLE orders ADD INDEX idx_user_created(user_id, created_at);

EXPLAIN SELECT * FROM orders WHERE user_id = 100 ORDER BY created_at;
-- Extra: Using index condition

案例 3:利用覆盖索引

-- 原 SQL(需要回表)
EXPLAIN SELECT id, name, email FROM users WHERE name = 'test';
-- Extra: NULL

-- 创建覆盖索引
ALTER TABLE users ADD INDEX idx_name_email(name, email);

EXPLAIN SELECT id, name, email FROM users WHERE name = 'test';
-- Extra: Using index

案例 4:多表 JOIN 优化

EXPLAIN
SELECT o.id, c.name, c.email
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id
WHERE o.created_at > '2024-01-01';

分析要点:

  1. 检查每个表的 type,尽量达到 eq_ref 或 ref
  2. 确认 JOIN 字段都有索引
  3. 关注 rows 的乘积,这是预估的总扫描行数

五、EXPLAIN 使用技巧

1. 结合 SHOW WARNINGS 查看优化后的 SQL

EXPLAIN SELECT * FROM users WHERE id IN (1, 2, 3);
SHOW WARNINGS;

SHOW WARNINGS 可以显示 MySQL 优化器重写后的查询语句。

2. 使用 FORMAT=JSON 获取更多信息

EXPLAIN FORMAT=JSON SELECT * FROM users WHERE id = 1\G

JSON 格式包含更多细节,如成本估算(cost_info)、实际使用的 key_parts 等。

3. 使用 EXPLAIN ANALYZE 获取实际执行数据

EXPLAIN ANALYZE SELECT * FROM users WHERE status = 1;

EXPLAIN ANALYZE 会实际执行查询,显示每个步骤的真实耗时和行数。

六、总结

EXPLAIN 是 MySQL 性能分析的必备工具,重点关注以下几个方面:

关注点说明
type至少达到 range 级别,最好是 ref 或更好
key确保使用了合适的索引
rows数值越小越好
Extra避免 Using filesort 和 Using temporary

优化建议:

  1. 添加合适的索引:针对 WHERE、JOIN、ORDER BY 的字段
  2. 利用覆盖索引:避免回表查询
  3. 避免索引失效:不要对索引列使用函数、不要前缀模糊查询
  4. 优化 JOIN:小表驱动大表,确保关联字段有索引

参考资料

Comments

Join the discussion — requires a GitHub account