MySQL explain执行计划每个字段的意义
ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花
MySQL的EXPLAIN语句用于查看SQL语句的执行计划,帮助开发者分析SQL的执行效率、优化查询性能。执行EXPLAIN后会返回多个字段,每个字段对应执行计划的不同维度,以下是每个字段的详细意义(基于MySQL 5.7及以上版本,兼顾常见场景)。
1. id
表示查询中每个select子句的执行顺序,核心规则如下:
- id相同:执行顺序由上至下(同一层级的查询,按书写顺序执行);
- id不同:id值越大,执行优先级越高(先执行id大的子查询,再执行id小的主查询);
- id为NULL:表示这是一个临时表的操作(如union结果的合并),不属于任何select子句。
实用提示:若id出现断层或异常,可能是子查询嵌套过深,需考虑优化(如改为join)。
2. select_type
表示当前select语句的类型,决定了查询的复杂程度,常见取值及意义如下:
- SIMPLE:简单查询,不包含子查询、union、视图等,是最基础的查询类型;
- PRIMARY:主查询,当查询包含子查询时,最外层的select语句会标记为PRIMARY;
- SUBQUERY:子查询(非关联子查询),位于select或where子句中的子查询,会被标记为SUBQUERY;
- DERIVED:派生表,由子查询生成的临时表(如from子句中的子查询),会标记为DERIVED;
- UNION:union语句中第二个及以后的select子句(第一个select会标记为PRIMARY);
- UNION RESULT:union查询的结果集合并操作,id通常为NULL;
- DEPENDENT SUBQUERY:关联子查询,子查询的执行依赖于外层查询的结果(效率较低,需谨慎使用);
- DEPENDENT UNION:union语句中,第二个及以后的select子句,且依赖于外层查询的结果。
3. table
表示当前执行步骤所操作的表(或临时表),常见取值说明:
- 直接显示表名:表示操作的是实际存在的表;
- 显示derivedN(如derived2):表示操作的是id为N的派生表(临时表);
- 显示unionN,M(如union1,2):表示操作的是id为N和M的union结果集;
- 显示NULL:表示不涉及具体表(如只查询常量、函数计算,无from子句)。
4. type
表示MySQL在表中查找数据的方式(即访问类型),是判断查询效率的核心字段之一,取值从优到差排序如下(重点记住前4种):
- system:表中只有一行数据(如系统表),是const类型的特例,效率最高;
- const:通过主键或唯一索引查询,最多返回一行数据,效率极高(如where id=1);
- eq_ref:多表join时,被join的表通过主键或唯一索引匹配,每行主表数据只匹配一行从表数据(如join on a.id = b.id,b.id是主键);
- ref:通过非唯一索引或唯一索引的前缀查询,可能返回多行数据(如where name='张三',name是普通索引);
- fulltext:通过全文索引查询(仅适用于fulltext索引字段);
- ref_or_null:与ref类似,但允许查询字段为NULL的情况;
- index_merge:MySQL会合并多个索引的查询结果(如where id=1 or name='张三',id和name分别有索引);
- unique_subquery:子查询中使用唯一索引,替代eq_ref的一种形式;
- index_subquery:子查询中使用非唯一索引,替代ref的一种形式;
- range:通过索引范围查询(如where id between 1 and 10、where id > 5),效率中等;
- index:全索引扫描(遍历整个索引树,不访问表数据),比全表扫描略优(索引文件比数据文件小);
- ALL:全表扫描(遍历整个表的所有数据),效率最低,需尽量避免(尤其是大表)。
实用提示:正常优化目标是将type优化到ref及以上,避免出现ALL(全表扫描)和index(全索引扫描)。
5. possible_keys
表示MySQL可能会使用的索引(候选索引),即MySQL在执行查询时,认为可能适用的索引列表。
注意事项:
- 该字段显示的索引,不一定会被实际使用(最终使用的索引看key字段);
- 若该字段为NULL,表示没有可用的索引,MySQL会直接走全表扫描;
- 即使有候选索引,若索引选择性太差(如字段值重复率高,如性别字段),MySQL也可能放弃使用索引,走全表扫描。
6. key
表示MySQL实际使用的索引(真正用于查询的索引),是判断索引是否生效的核心字段。
注意事项:
- 若key为NULL,表示MySQL没有使用任何索引,走了全表扫描;
- key的值一定是possible_keys中的一个(或多个,若使用index_merge);
- 若使用了覆盖索引(索引包含查询所需的所有字段),key会显示该索引,且Extra字段会出现“Using index”。
7. key_len
表示MySQL实际使用的索引长度(单位:字节),用于判断索引的使用情况(如联合索引是否被部分使用)。
核心说明:
- key_len越长,说明索引使用的字段越多(联合索引中,前缀匹配的字段越多,key_len越长);
- key_len的计算与字段类型、字符集有关(如varchar(20) utf8mb4,每个字符占4字节,key_len=20*4=80;若字段允许NULL,会额外加1字节);
- 通过key_len可判断联合索引是否被完全使用(如联合索引(a,b,c),若key_len对应a+b的长度,说明只使用了前两个字段)。
8. ref
表示与key字段中索引匹配的列或常量,说明索引是如何被使用的,常见取值:
- const:表示索引匹配的是一个常量(如where id=1,ref为const);
- 表名.字段名:表示索引匹配的是另一张表的某个字段(如a join b on a.id = b.a_id,b表的ref为a.id);
- NULL:表示索引使用的是范围查询(type为range)或全索引扫描(type为index),无需匹配具体值。
9. rows
表示MySQL预估的、需要扫描的行数(非实际扫描行数),用于判断查询的效率:rows值越小,扫描的行数越少,效率越高。
注意事项:
- rows是MySQL根据统计信息估算的值,可能与实际扫描行数有差异,但趋势一致;
- 若rows值远大于表的实际行数,说明MySQL的统计信息过时,需执行ANALYZE TABLE 表名更新统计信息。
10. Extra
表示执行计划的额外信息,包含大量关键的优化提示,是分析SQL性能的重要补充,常见取值及意义如下:
- Using index:使用了覆盖索引(索引包含查询所需的所有字段),无需回表查询数据,效率极高(最优情况);
- Using where:MySQL在获取数据后,会对数据进行where条件过滤(说明where条件生效,但可能未使用索引);
- Using temporary:MySQL需要创建临时表来存储查询结果(如group by、distinct、union等操作),效率较低,需优化;
- Using filesort:MySQL需要对结果集进行排序(未使用索引排序),效率较低,需优化(如添加排序索引);
- Using join buffer (Block Nested Loop):多表join时,MySQL使用join buffer来存储中间结果,效率较低(通常是因为没有可用的join索引);
- Using index condition:使用了索引条件推送(ICP),MySQL会先通过索引过滤部分数据,再回表查询,减少回表次数;
- Using intersect:使用了索引交集(index_merge的一种,多个索引的交集查询);
- Using union:使用了索引并集(index_merge的一种,多个索引的并集查询);
- Impossible WHERE:where条件永远为false(如where 1=0),MySQL不会扫描任何数据;
- No tables used:查询不涉及任何表(如select 1+1)。
实用提示:Extra中出现Using temporary、Using filesort通常是性能瓶颈,需优先优化;出现Using index是最优状态。
补充说明
1. 执行计划的字段顺序不影响分析,核心关注type、key、rows、Extra四个字段;
2. 若执行计划中出现ALL(全表扫描)、Using temporary、Using filesort,需优先优化(如添加合适的索引、调整SQL语句);
3. 不同MySQL版本的执行计划字段可能略有差异,但核心字段(id、type、key、rows、Extra)的意义基本一致。
案例拆解:结合具体SQL理解执行计划字段
以下选取3个高频SQL场景(单表查询、多表JOIN、子查询),模拟执行计划输出,逐一拆解各字段的实际对应关系,帮你快速关联理论与实践。
案例1:单表查询(主键+普通索引场景)
1. 准备测试表与数据
-- 创建用户表,主键id,普通索引name
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`age` INT NOT NULL,
`gender` VARCHAR(10) DEFAULT NULL,
INDEX idx_name (`name`) -- 普通索引
);
-- 插入测试数据
INSERT INTO `user` (name, age, gender) VALUES
('张三', 25, '男'), ('李四', 30, '女'), ('王五', 28, '男');
2. 执行SQL与模拟执行计划
执行SQL:EXPLAIN SELECT id, name FROM `user` WHERE id = 1 AND name = '张三';
模拟执行计划输出(重点字段):
1 | SIMPLE | user | const | PRIMARY,idx_name | PRIMARY | 4 | const | 1 | Using index |
3. 字段拆解(对应前文理论)
- id=1:只有一个select子句,执行顺序唯一,从上至下执行。
- select_type=SIMPLE:简单查询,无subquery、union,仅单表查询。
- table=user:当前操作的是实际存在的user表。
- type=const:通过主键id查询,最多返回一行数据,效率极高(符合前文const是主键/唯一索引查询的定义)。
- possible_keys=PRIMARY,idx_name:MySQL认为可能适用的索引的是主键索引(PRIMARY)和普通索引(idx_name)。
- key=PRIMARY:实际使用的是主键索引(主键索引查询比普通索引更高效,MySQL优先选择)。
- key_len=4:id是INT类型(占4字节),不允许NULL,因此key_len=4(符合前文“key_len与字段类型相关”的说明)。
- ref=const:索引匹配的是常量(where id=1中的1是常量)。
- rows=1:MySQL预估扫描1行数据(与实际数据量一致,统计信息准确)。
- Extra=Using index:查询的字段(id、name)都在主键索引和idx_name索引中(覆盖索引),无需回表,效率最优。
案例2:多表JOIN查询(关联索引场景)
1. 准备测试表(新增订单表)
-- 创建订单表,外键user_id关联user表id
CREATE TABLE `order` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`order_no` VARCHAR(50) NOT NULL,
`user_id` INT NOT NULL,
`create_time` DATETIME NOT NULL,
INDEX idx_user_id (`user_id`) -- 外键索引,关联user.id
);
-- 插入测试数据
INSERT INTO `order` (order_no, user_id, create_time) VALUES
('OD20260313001', 1, '2026-03-13 10:00:00'),
('OD20260313002', 2, '2026-03-13 10:30:00');
2. 执行SQL与模拟执行计划
执行SQL:EXPLAIN SELECT u.name, o.order_no FROM `user` u JOIN `order` o ON u.id = o.user_id WHERE u.age > 20;
模拟执行计划输出(重点字段):
1 | SIMPLE | u | ALL | PRIMARY | NULL | NULL | NULL | 3 | Using where |
1 | SIMPLE | o | ref | idx_user_id | idx_user_id | 4 | test.u.id | 1 | NULL |
3. 字段拆解(重点关注多表关联相关字段)
- id=1(两行):id相同,执行顺序由上至下(先执行user表查询,再执行order表关联查询)。
- select_type=SIMPLE:虽为多表JOIN,但无复杂子查询、union,仍为简单查询。
- table=u、o:分别对应user表(别名u)和order表(别名o)。
- type=ALL(u表)、ref(o表): u表:where条件是age>20,age无索引,因此type=ALL(全表扫描);
- o表:通过idx_user_id索引关联u.id,type=ref(非唯一索引查询,匹配多行但每行主表数据对应一行从表数据)。
possible_keys与key: u表:possible_keys=PRIMARY(主键索引),但未使用(key=NULL),因查询条件不涉及主键;
o表:possible_keys=idx_user_id,实际使用该索引(key=idx_user_id)。
ref=test.u.id(o表):o表的索引(idx_user_id)匹配的是u表的id字段(多表关联的典型ref取值)。
rows=3(u表)、1(o表):u表全表扫描3行,o表每匹配一个u.id,预估扫描1行。
Extra=Using where(u表):u表全表扫描后,通过where age>20过滤数据(where条件生效但未用索引)。
案例3:子查询(派生表+关联子查询场景)
1. 执行SQL与模拟执行计划
执行SQL:EXPLAIN SELECT u.name, (SELECT COUNT(*) FROM `order` o WHERE o.user_id = u.id) AS order_count FROM `user` u WHERE u.name = '张三';
模拟执行计划输出(重点字段):
1 | PRIMARY | u | ref | idx_name | idx_name | 202 | const | 1 | NULL |
2 | DEPENDENT SUBQUERY | o | ref | idx_user_id | idx_user_id | 4 | test.u.id | 1 | Using index |
3. 字段拆解(重点关注id、select_type)
- id=1、2:id不同,id=2(子查询)优先级高于id=1(主查询),先执行子查询,再执行主查询。
- select_type=PRIMARY、DEPENDENT SUBQUERY: id=1:主查询,标记为PRIMARY;
- id=2:关联子查询(子查询o.user_id = u.id依赖主查询u.id的值),标记为DEPENDENT SUBQUERY。
type=ref(两表): u表:通过idx_name(name索引)查询,type=ref;
o表:通过idx_user_id索引关联u.id,type=ref。
key_len=202(u表):name是varchar(50)、utf8mb4(50*4=200字节),不允许NULL,因此key_len=200(无额外1字节)。
Extra=Using index(o表):子查询只统计count(*),idx_user_id索引包含user_id字段(覆盖索引),无需回表。
案例4:包含Using temporary/Using filesort的场景(分组+排序无索引)
1. 执行SQL(触发性能瓶颈)
基于前文user表(gender无索引),执行SQL:EXPLAIN SELECT gender, COUNT(*) AS count FROM `user` GROUP BY gender ORDER BY count DESC;
说明:该SQL需按gender分组统计数量,再按数量降序排序,因gender无索引、排序字段count无索引,会触发Using temporary和Using filesort。
2. 模拟执行计划输出(重点字段)
1 | SIMPLE | user | ALL | NULL | NULL | NULL | NULL | 3 | Using temporary; Using filesort |
3. 字段拆解(重点关注Extra字段及性能瓶颈)
- id=1、select_type=SIMPLE:单表简单查询,无复杂子查询、JOIN,执行顺序唯一。
- table=user:操作的是user表,无临时表(临时表是MySQL内部创建,table字段不显示)。
- type=ALL、key=NULL:gender字段无索引,group by需要全表扫描所有数据,因此type=ALL(全表扫描),无可用索引(key=NULL)。
- possible_keys=NULL:MySQL认为无适用索引,无法通过索引优化分组操作。
- rows=3:预估扫描3行数据(与user表实际数据量一致)。
- Extra=Using temporary; Using filesort(核心重点):Using temporary:因group by gender无索引,MySQL需要创建临时表,存储分组后的结果(按gender分组,统计count(*)),临时表的创建和销毁会消耗性能;Using filesort:因order by count DESC(排序字段count是聚合函数结果,无索引),MySQL无法通过索引排序,只能将分组后的结果加载到内存或磁盘中进行文件排序,效率较低。
4. 优化提示(关联前文理论)
要消除Using temporary和Using filesort,可添加单列索引(注:当前仅针对gender字段优化,无需联合索引):CREATE INDEX idx_gender ON `user` (gender);
优化后执行计划变化:type会变为ref(若gender值有重复,也可能为range),key=idx_gender,Extra字段会消失(无临时表、无文件排序),查询效率大幅提升,对应前文“Extra中出现Using temporary、Using filesort需优先优化”的提示。
5. 优化后执行计划对比(核心重点)
添加索引后,执行相同SQL:EXPLAIN SELECT gender, COUNT(*) AS count FROM `user` GROUP BY gender ORDER BY count DESC;
优化后模拟执行计划输出(与未优化版本对比):
id | 1 | 1 | 无变化,始终为单select子句,执行顺序唯一 |
select_type | SIMPLE | SIMPLE | 无变化,仍为单表简单查询,无复杂逻辑 |
table | user | user | 无变化,始终操作user表 |
type | ALL(全表扫描) | ref(非唯一索引查询) | 核心优化:从全表扫描(低效)变为索引查询(高效),符合前文type优化到ref及以上”的目标 |
possible_keys | NULL | idx_gender | 新增可用索引,MySQL识别到idx_gender可用于分组操作 |
key | NULL | idx_gender | 核心优化:实际使用idx_gender索引,索引生效,对应前文“key字段表示实际使用的索引” |
key_len | NULL | 42 | gender是varchar(10)、utf8mb4(10*4=40字节),允许NULL(+1字节),实际key_len=41,此处取近似值42,符合前文key_len计算规则 |
ref | NULL | NULL | 无变化,因group by使用索引范围扫描,无需匹配具体常量或其他表字段 |
rows | 3 | 3 | 数据量较小,预估扫描行数无变化,但实际查询效率提升(无需全表扫描和临时操作) |
Extra | Using temporary; Using filesort | NULL | 核心优化:消除性能瓶颈,无需创建临时表和文件排序,对应前文Extra出现这两个值需优先优化”的提示 |
6. 对比总结
通过添加idx_gender索引,实现了3个关键优化,完美呼应前文理论:
- 访问类型(type)从ALL(全表扫描)优化为ref(索引查询),符合“type优化到ref及以上”的目标;
- 索引从无(key=NULL)变为实际使用(key=idx_gender),索引生效,减少扫描开销;
- 消除了Using temporary和Using filesort两个性能瓶颈,查询效率大幅提升,印证了“添加合适索引可优化此类瓶颈”的结论。
案例总结
1. 执行计划的核心是「type(访问类型)、key(实际索引)、rows(预估行数)、Extra(优化提示)」,这四个字段直接决定查询效率;
2. 单表查询优先看type和key,尽量避免ALL(全表扫描);多表JOIN优先看关联字段是否有索引(type=ref/eq_ref);
3. 子查询的id和select_type能明确执行顺序和子查询类型,关联子查询(DEPENDENT SUBQUERY)效率较低,可考虑改为JOIN优化;
4. Using temporary(临时表)和Using filesort(文件排序)是常见性能瓶颈,多出现于group by、order by且无对应索引的场景,添加合适索引可有效优化。
ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花
本专栏聚焦MySQL性能优化实战,从SQL编写、索引设计、参数配置到架构优化,系统讲解慢查询分析、高并发场景解决方案。用通俗语言拆解底层原理,搭配真实案例与可落地技巧,帮你快速定位瓶颈、提升查询效率与系统稳定性。无论开发、运维还是DBA,都能从零掌握MySQL调优核心能力,轻松应对生产环境性能问题。
查看20道真题和解析