本文基于 MySQL 5.7+ 和 8.0+ 版本梳理,适用于大多数 MySQL 版本。建议收藏,方便日后 SQL 优化查阅。
一、Explain 是什么?
Explain 是 MySQL 提供的查询执行计划分析工具 。它通过模拟优化器的执行过程,告诉你一条 SQL 语句将如何读取数据、是否使用索引、扫描多少行等关键信息,是 SQL 性能优化的"透视镜"。
核心作用
-
查看表的读取顺序:了解多表关联时谁先谁后。
-
判断索引使用情况:确认是否走索引,避免全表扫描。
-
预估查询成本 :通过
rows字段估算扫描行数,定位性能瓶颈。 -
识别额外操作 :发现
Using filesort(文件排序)、Using temporary(临时表)等性能杀手。
二、Explain 用法与语法
1. 基础用法(最常用)
直接在 SQL 语句前加上 EXPLAIN即可。
-- 分析单表查询
EXPLAIN SELECT * FROM user WHERE age > 18;
-- 分析多表连接
EXPLAIN SELECT u.name, o.amount
FROM user u
JOIN orders o ON u.id = o.user_id
WHERE u.city = 'Beijing';
2. 进阶用法(MySQL 8.0+)
MySQL 8.0 提供了更强大的分析能力。
-- 1. 实际执行并分析(显示实际耗时,MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT * FROM user WHERE age = 25;
-- 2. JSON 格式输出(包含更详细的成本模型数据)
EXPLAIN FORMAT=JSON SELECT * FROM user WHERE age = 25;
三、Explain 返回列详解(核心)
执行 EXPLAIN后,你会看到一张包含约 12 列的结果表。以下是每列的核心解读 与实战关注点。
| 列名 | 含义 | 核心关注点 |
|---|---|---|
| id | 查询序列号 | 判断执行顺序(id 越大越先执行) |
| select_type | 查询类型 | 区分简单查询、子查询、UNION |
| table | 访问的表名 | 当前行数据属于哪张表 |
| partitions | 匹配的分区 | 若为分区表,显示命中的分区 |
| type | 访问类型 | 性能关键!(如 const, ref, ALL) |
| possible_keys | 可能使用的索引 | 优化器理论可选的索引 |
| key | 实际使用的索引 | 实际走的索引(NULL 表示未走索引) |
| key_len | 索引长度 | 判断联合索引中实际使用了多少列 |
| ref | 索引引用 | 显示与索引比较的列或常量 |
| rows | 预估扫描行数 | 越小越好,是估算值 |
| filtered | 过滤百分比 | 表示条件过滤后剩余数据的比例 |
| Extra | 额外信息 | 性能优化的关键提示(如 Using index) |
四、核心列深度解析(附 SQL 示例)
1. type列:访问类型(性能核心)
type表示 MySQL 如何查找表中的行,性能从优到劣排序如下:
| 类型 | 含义 | 示例 SQL(假设有索引) |
|---|---|---|
| system | 表只有一行(系统表) | SELECT * FROM mysql.time_zone WHERE ... |
| const | 最优,通过主键/唯一索引一次找到 | SELECT * FROM user WHERE id = 1 |
| eq_ref | 多表 JOIN 时,被驱动表使用主键/唯一索引 | SELECT * FROM a JOIN b ON a.id = b.id(b.id 是主键) |
| ref | 常见,使用普通索引等值匹配(可能多行) | SELECT * FROM user WHERE age = 25(age 有索引) |
| range | 索引范围扫描(BETWEEN, IN, >, <) | SELECT * FROM user WHERE age > 20 |
| index | 全索引扫描(遍历索引树) | SELECT age FROM user(age 有索引) |
| ALL | 全表扫描(性能最差,需优化) | SELECT * FROM user WHERE name = 'John'(name 无索引) |
优化建议 :生产环境至少应达到 range级别,尽量避免 ALL(全表扫描)。
2. key与 possible_keys:索引使用
-
possible_keys :理论上可能用到的索引(仅供参考)。
-
key :实际使用的索引。
常见问题分析:
-
key为 NULL:说明未使用索引,需检查查询条件或建索引。 -
possible_keys有值但key为 NULL:通常因为数据量小或索引区分度低,优化器认为全表扫描更快。
3. Extra列:额外信息(优化关键)
Extra列提供了极其重要的性能线索。
| 值 | 含义 | 优化建议 |
|---|---|---|
| Using index | 覆盖索引(极佳) | 查询列全在索引中,无需回表。 |
| Using where | 使用 WHERE 过滤 | 正常情况,表示在存储引擎层或 Server 层进行了过滤。 |
| Using filesort | 文件排序(需优化) | 无法利用索引排序,需额外排序操作。考虑为 ORDER BY字段加索引。 |
| Using temporary | 临时表(需优化) | 常见于 GROUP BY、DISTINCT操作。需为分组字段加索引。 |
| Using join buffer | 使用连接缓存 | 连接条件未使用索引,导致使用内存缓存。 |
4. rows与 filtered:扫描成本
-
rows :MySQL 预估需要扫描的行数(估算值 ,非精确值)。越小越好。
-
filtered :表示经过
WHERE条件过滤后,剩余行数的百分比。rows * filtered可估算连接查询中下一张表需要处理的行数。
五、实战案例:SQL 优化前后对比
场景:无索引导致全表扫描(type=ALL)
问题 SQL:
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
执行计划分析:
| id | type | key | rows | Extra |
|---|---|---|---|---|
| 1 | ALL | NULL | 10000 | Using where |
诊断 :type=ALL表示全表扫描,key=NULL表示未使用索引,rows=10000表示扫描了 1 万行,性能极差。
优化方案 :为 user_id添加索引。
CREATE INDEX idx_user_id ON orders(user_id);
优化后执行计划:
| id | type | key | rows | Extra |
|---|---|---|---|---|
| 1 | ref | idx_user_id | 1 | Using index |
优化效果 :type从 ALL提升为 ref(索引等值匹配),rows从 10000 降为 1,性能提升千倍。
六、总结:Explain 分析 checklist
-
看
type:是否出现ALL(全表扫描)?必须优化。 -
看
key:是否实际使用了索引?key不应为 NULL。 -
看
Extra:是否出现Using filesort或Using temporary?这两个是性能杀手,需通过调整索引或 SQL 写法消除。 -
看
rows:预估扫描行数是否过大?过大说明索引可能失效或缺失。
版权声明:本文为 CSDN 博主原创,转载请注明出处。如有疑问,欢迎评论区交流。
参考来源:MySQL 官方文档、腾讯云开发者社区、脚本之家等。