执行计划的核心作用
执行计划是 MySQL 性能调优的核心工具,通过 EXPLAIN 命令可以查看 SQL 语句的执行逻辑——包括 MySQL 优化器如何解析 SQL、是否使用索引、表的访问顺序、数据过滤方式等关键信息。
EXPLAIN 不会实际执行 SQL,仅返回优化器的执行方案,虽不直接提供调优建议,但能帮助开发者定位性能瓶颈(如全表扫描、未使用索引、临时表过多等),为 SQL 优化和索引设计提供重要依据。
基础使用语法
EXPLAIN SELECT 语句; -- 普通查询
EXPLAIN EXTENDED SELECT 语句; -- 扩展输出,可结合 SHOW WARNINGS 查看优化后的 SQL
EXPLAIN FORMAT=JSON SELECT 语句; -- JSON 格式输出,更详细的执行计划信息
示例输出(以简单查询为例)
MySQL [blog]> explain select * from articles \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: articles
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 197
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
执行计划各字段详解
1. id:查询执行顺序标识
-
含义:一组数字,代表 MySQL 执行查询操作的顺序,核心规则如下:
-
id 值越大,执行优先级越高(先执行 id 大的操作);
-
id 值相同时,按字段从上到下的顺序执行;
-
若 id 为 NULL,通常是 Union 结果集的合并操作。
-
-
核心作用:判断复杂查询(含子查询、表连接)中各部分的执行顺序,定位子查询或连接操作的优先级。
2. select_type:查询类型
- 含义:标识查询中每个 SELECT 子句的类型,反映查询的复杂程度(如简单查询、子查询、Union 等),常见值及说明如下:
| select_type | 核心说明 |
|---|---|
| SIMPLE | 简单查询,不包含子查询、表连接、Union 等复杂语法(最常见)。 |
| PRIMARY | 复杂查询中最外层的查询(若包含子查询、Union,外层 SELECT 标记为 PRIMARY)。 |
| SUBQUERY | 位于 SELECT 或 WHERE 列表中的子查询。 |
| DERIVED | 位于 FROM 列表中的子查询(衍生表),即查询结果作为临时表供外层使用。 |
| UNION | Union 语句中第二个及后续的 SELECT 子句。 |
| UNION RESULT | 从 Union 合并后的临时表中获取结果的查询。 |
| DEPENDENT SUBQUERY | 依赖外层查询结果的子查询(子查询的执行需依赖外层查询的参数)。 |
| DEPENDENT UNION | 依赖外层查询结果的 Union 子句。 |
| UNCACHEABLE UNION | 无法缓存结果的 Union 子句,需每次重新执行。 |
| UNCACHEABLE QUERY | 无法缓存结果的查询(如包含非确定性函数的子查询)。 |
3. table:查询涉及的表
-
含义:显示当前行执行的操作对应的表名(或临时表、衍生表标识):
-
直接显示物理表名(如示例中的
articles); -
若为衍生表,显示
derived(N)(N 为对应子查询的 id); -
若为 Union 临时表,显示
union(M,N)(M、N 为 Union 中各子查询的 id)。
-
4. partitions:分区信息
-
含义:显示查询涉及的表分区(仅对分区表有效),非分区表显示为 NULL。
-
作用:判断查询是否命中分区索引,优化分区表的查询效率(如避免全分区扫描)。
5. type:表访问方式(核心性能指标)
- 含义:MySQL 从表中获取数据的方式(也称“访问类型”),直接决定查询性能,性能从差到好排序如下:
ALL < index < range < ref < eq_ref < const < system < NULL
| type | 核心说明 | 性能等级 | 典型场景 |
|---|---|---|---|
| ALL | 全表扫描,遍历整个表的所有行查找匹配数据(无索引可用)。 | 最差 | 未建索引、查询条件未命中索引(如 select * from user)。 |
| index | 索引全扫描,遍历整个索引树(无需访问表数据),比 ALL 效率高(索引通常比表小)。 | 较差 | 查询字段均为索引列,但未限定范围(如 select id from user order by id)。 |
| range | 索引范围扫描,仅扫描索引中特定范围的行(非全索引遍历)。 | 中等 | 用到 between、<、>、<=、>=、in 等范围条件(如 select * from user where id between 1 and 100)。 |
| ref | 非唯一索引扫描,返回匹配单个值的所有行(如普通索引、唯一索引前缀)。 | 较好 | 单表查询用非唯一索引过滤(如 select * from user where name = '张三'),或表连接用非主键作为连接条件。 |
| eq_ref | 唯一索引扫描,每个索引键仅对应表中一条记录(最精准的索引访问)。 | 很好 | 多表连接用主键或唯一索引作为连接条件(如 select * from user u join order o on u.id = o.user_id,u.id 为主键)。 |
| const | 查询转化为常量,单表中仅一条匹配行(主键/唯一索引等值查询)。 | 优秀 | 主键或唯一索引等值查询(如 select * from user where id = 1)。 |
| system | 表中仅一行数据(空表或单行列),仅支持 MyISAM/Memory 引擎(InnoDB 不显示)。 | 最优 | MyISAM 引擎的单一行表查询(极少场景)。 |
| NULL | 无需访问表/索引,直接通过优化器计算得到结果。 | 最优 | 仅查询聚合函数且可通过索引直接计算(如 select max(id) from user,id 为索引)。 |
- 核心作用:快速判断查询是否有效使用索引,若出现
ALL(全表扫描)且数据量较大,需优先优化(如添加索引、调整查询条件)。
6. possible_keys:可能使用的索引
-
含义:MySQL 优化器认为当前查询可能适用的索引列表(仅为“候选”,不一定实际使用)。
-
注意:若该字段为 NULL,说明无候选索引,可能导致全表扫描(需检查查询条件是否可建索引)。
7. key:实际使用的索引
-
含义:MySQL 执行查询时实际采用的索引(若未使用索引,显示为 NULL)。
-
核心价值:对比
possible_keys和key,判断优化器是否选对了索引:-
若
possible_keys非空但key为 NULL,可能是索引失效(如索引列用函数、隐式转换),或数据量太小无需用索引; -
若
key显示的索引不是预期的,可通过force index(索引名)强制使用指定索引,或优化索引设计。
-
8. key_len:使用的索引长度
-
含义:表示查询中使用的索引字段的总长度(单位:字节),可通过该值判断多列索引中实际用到的列数。
-
计算规则(常见字段类型):
-
字符型:
utf8编码占 3 字节/字符,gbk占 2 字节/字符;varchar需额外加 2 字节(存储长度); -
数值型:
int占 4 字节,bigint占 8 字节; -
允许为空(NULL)的字段,需额外加 1 字节。
-
-
示例:若索引为
idx_name_age (name, age),name是varchar(10) utf8(允许为空),age是int(不允许为空):-
仅用
name过滤时,key_len = 10*3 + 2(varchar) + 1(NULL)= 33; -
同时用
name+age过滤时,key_len = 33 + 4(int)= 37。
-
-
作用:验证多列索引是否被充分利用(若
key_len未包含所有索引列,可能是查询条件未覆盖后续列)。
9. ref:索引匹配条件
-
含义:显示与
key对应的索引列进行匹配的条件(如常量、其他表的列名)。 -
示例:
-
单表查询
where name = '张三',ref显示const(常量匹配); -
表连接
u.id = o.user_id,ref显示blog.o.user_id(其他表的列匹配)。
-
10. rows:估算扫描行数
-
含义:MySQL 根据表统计信息(如索引 cardinality)和索引使用情况,估算找到目标结果集需扫描的行数(非精确值)。
-
核心作用:行数越少,查询效率越高。若
rows远大于实际结果行数,可能是统计信息过时,需执行ANALYZE TABLE 表名更新统计信息。
11. filtered:过滤比例
-
含义:一个百分比值,表示经过条件过滤后,剩余行数占
rows(估算扫描行数)的比例(filtered = 剩余行数 / rows * 100)。 -
作用:与
rows配合判断过滤效果——filtered越高,说明条件过滤越高效(无需扫描过多无效数据)。
12. Extra:额外执行信息(关键优化依据)
- 含义:包含无法在其他字段显示,但对性能分析至关重要的额外信息,常见值及说明如下:
| Extra 取值 | 核心说明 | 优化建议 |
|---|---|---|
| Using where | 存储引擎返回数据后,MySQL 对结果集进行“后过滤”(where 条件未命中索引)。 | 若 type 为 ALL,需添加索引让条件命中索引,避免全表扫描后过滤。 |
| Using temporary | 需要创建临时表存储结果集(如排序、分组、Union),性能开销大。 | 优化排序/分组字段(如添加索引),避免 distinct 或 group by 非索引列。 |
| Using filesort | 无法利用索引完成排序,需通过“文件排序”(外部排序),性能差。 | 调整 order by 字段,使其与索引列顺序一致(利用索引排序)。 |
| Using index | 仅使用索引即可满足查询(覆盖索引),无需访问表数据(性能最优)。 | 保持查询字段为索引列,避免 select *(减少回表操作)。 |
| Using join buffer | 表连接时未使用索引,需用连接缓冲区存储中间结果(效率低)。 | 为连接条件添加索引(如 on u.id = o.user_id,给 o.user_id 建索引)。 |
| Impossible where | where 条件恒为 false,无符合条件的行(如 where id = 1 and id = 2)。 |
检查查询条件是否合理,避免无效查询。 |
| Select tables optimized away | 仅通过索引即可直接返回聚合函数结果(如 select max(id) from user)。 |
无需优化,属于最优执行计划。 |
| Distinct | 找到第一个匹配行后,停止搜索相同值(如 select distinct name from user)。 |
无需优化,是高效的去重方式。 |
| Index merges | 对单个表使用多个索引,并合并索引结果(如 where id = 1 or name = '张三',id 和 name 均为索引)。 |
若索引合并效率低,可调整查询条件或创建联合索引。 |
执行计划分析核心步骤
-
看 type:优先关注是否为
ALL(全表扫描)或index(索引全扫描),若数据量较大,需优化索引; -
看 key:确认实际使用的索引是否符合预期,避免“可能的索引”未被使用;
-
看 rows 和 filtered:判断扫描行数和过滤效果,行数过多或过滤比例过低需优化条件;
-
看 Extra:警惕
Using temporary、Using filesort(需优先优化),追求Using index(覆盖索引); -
结合 id 和 select_type:理清复杂查询的执行顺序,定位子查询或连接操作的瓶颈。
常见性能问题及优化方向
| 执行计划特征 | 问题本质 | 优化方向 |
|---|---|---|
| type = ALL + Using where | 全表扫描后过滤 | 给 where 条件字段添加索引;优化条件(避免函数操作索引列,如 where date(create_time) = '2024-01-01' 改为 where create_time between '2024-01-01 00:00:00' and '2024-01-01 23:59:59')。 |
| Using temporary + Using filesort | 临时表 + 文件排序 | 给 group by/order by 字段添加联合索引;避免 select *,仅查询必要字段。 |
| possible_keys 非空 + key 为 NULL | 索引失效 | 检查索引列是否有隐式转换(如字符串索引用数值条件)、函数操作、模糊查询前缀通配符(like '%xxx'),调整查询条件或索引。 |
| Using join buffer | 表连接无索引 | 给连接条件字段(如 on a.id = b.a_id 中的 b.a_id)添加索引。 |


