MySQL EXPLAIN 深度解析与优化
一、MySQL EXPLAIN 是什么?
EXPLAIN 是 MySQL 中用于分析 SQL 执行计划的核心命令,它能告诉你 MySQL 优化器会如何执行这条 SQL(比如用什么索引、扫描多少行、连接方式等),是优化慢 SQL 的必备工具。
可以把它理解为:你写了一条 SQL 想让 MySQL 执行,EXPLAIN 会提前告诉你 MySQL 的"执行思路"------ 走哪条路、做哪些操作、效率怎么样,帮你找到 SQL 里的性能瓶颈。
二、基本用法
1. 语法
sql
EXPLAIN + 你的SQL语句;
-- 示例
EXPLAIN SELECT * FROM user WHERE id = 1;
如果想查看更详细的执行计划(比如执行时的成本、临时表等),可以用:
sql
EXPLAIN ANALYZE SELECT * FROM user WHERE id = 1; -- MySQL 8.0 及以上支持
2. 输出字段说明
执行 EXPLAIN 后会返回一个结果集,核心字段如下(新手先掌握这 8 个核心字段即可):
| 字段 | 核心含义 |
|---|---|
| id | SQL 执行的顺序(子查询/联表时会有多个 id,数字越大越先执行) |
| select_type | 查询类型(比如简单查询、子查询、联表查询、衍生表等) |
| table | 本次执行涉及的表名 |
| type | 访问类型(核心!判断性能的关键,从差到好:ALL < index < range < ref < eq_ref < const/system) |
| possible_keys | MySQL 可能会选择的索引(候选索引) |
| key | MySQL 实际使用的索引(如果为 NULL,说明没用到索引) |
| rows | MySQL 预估要扫描的行数(数值越小越好) |
| Extra | 额外信息(比如 Using index 走覆盖索引、Using where 过滤条件、Using filesort 排序等) |
三、核心字段详解(新手重点)
1. type(访问类型)
这是 EXPLAIN 中最重要的字段,直接反映 SQL 的性能层级,常见值从差到优排序:
- ALL :全表扫描(最差!会遍历整个表),比如
SELECT * FROM user;且无任何条件。 - index:全索引扫描(比 ALL 好一点,但仍扫描整个索引),比如查询的字段都在索引里,但无过滤条件。
- range :索引范围扫描(比如用
>、<、BETWEEN、IN等),比如SELECT * FROM user WHERE id BETWEEN 1 AND 10;。 - ref :非唯一索引扫描(匹配多行),比如
SELECT * FROM user WHERE name = '张三';(name 是普通索引)。 - eq_ref :唯一索引扫描(匹配一行),比如联表查询时用主键/唯一索引关联,
SELECT * FROM user u JOIN order o ON u.id = o.user_id;。 - const/system :查询结果能确定为一行(最优),比如用主键查询
SELECT * FROM user WHERE id = 1;。
2. key(实际使用的索引)
- 如果
key为NULL,说明 MySQL 没用到索引,大概率是 SQL 写得有问题(比如用了函数操作索引字段、条件不匹配索引等)。 - 示例:如果
user表的id是主键(默认索引),执行EXPLAIN SELECT * FROM user WHERE id = 1;,key列会显示PRIMARY(主键索引名)。
3. Extra(关键提示)
- Using index:走了"覆盖索引"(查询的字段都在索引里,无需回表查数据),性能极佳。
示例:user 表有索引 idx_name_age (name, age),执行 EXPLAIN SELECT name, age FROM user WHERE name = '张三';,Extra 会显示 Using index。
- Using where:MySQL 会先扫描数据,再用 WHERE 条件过滤(如果同时有 Using index,说明先走索引再过滤)。
- Using filesort :MySQL 需额外做排序(不是用索引排序),性能差,比如
SELECT * FROM user ORDER BY name;但 name 无索引。 - Using temporary:MySQL 需创建临时表(比如 GROUP BY 没用到索引),性能差,要优化。
四、实战示例
假设有一张 user 表,结构如下:
go
CREATE TABLE `user` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`age` int,
`gender` tinyint,
INDEX `idx_name` (`name`) -- 普通索引
);
示例 1:全表扫描(差)
sql
EXPLAIN SELECT * FROM user WHERE age = 20;
type:ALL(全表扫描)key:NULL(没用到索引)Extra:Using where(扫描后过滤)- 优化:给
age加索引ALTER TABLE user ADD INDEX idx_age (age);。
示例 2:使用索引(好)
sql
EXPLAIN SELECT * FROM user WHERE name = '张三';
type:ref(非唯一索引扫描)key:idx_name(用到了 name 的索引)rows:预估扫描行数(比如 10 行,远小于全表行数)。
示例 3:覆盖索引(优)
sql
EXPLAIN SELECT name FROM user WHERE name = '张三';
type:refkey:idx_nameExtra:Using index(覆盖索引,无需回表)。
五、使用注意事项
EXPLAIN的rows是 MySQL 预估的扫描行数,不是实际行数,但能反映性能趋势(数值越小越好)。EXPLAIN只分析执行计划,不会实际执行 SQL(除非用EXPLAIN ANALYZE),所以可以放心在生产环境使用。- 即使
possible_keys有值,key也可能为 NULL ------ 说明 MySQL 认为走索引不如全表扫描快(比如表数据量极小)。
总结
EXPLAIN是分析 SQL 执行计划的核心工具,重点看type(访问类型)、key(实际索引)、Extra(额外提示)三个字段。- 优化目标:尽量让
type达到range及以上,key不为 NULL,避免Extra出现Using filesort/Using temporary。 - 最理想的执行计划:
type为 const/system +key有值 +Extra显示Using index。