MySQL 执行计划(EXPLAIN)

执行计划的核心作用

执行计划是 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_keyskey,判断优化器是否选对了索引:

    • 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)namevarchar(10) utf8(允许为空),ageint(不允许为空):

    • 仅用 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_idref 显示 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 条件未命中索引)。 typeALL,需添加索引让条件命中索引,避免全表扫描后过滤。
Using temporary 需要创建临时表存储结果集(如排序、分组、Union),性能开销大。 优化排序/分组字段(如添加索引),避免 distinctgroup 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 均为索引)。 若索引合并效率低,可调整查询条件或创建联合索引。

执行计划分析核心步骤

  1. 看 type:优先关注是否为 ALL(全表扫描)或 index(索引全扫描),若数据量较大,需优化索引;

  2. 看 key:确认实际使用的索引是否符合预期,避免“可能的索引”未被使用;

  3. 看 rows 和 filtered:判断扫描行数和过滤效果,行数过多或过滤比例过低需优化条件;

  4. 看 Extra:警惕 Using temporaryUsing filesort(需优先优化),追求 Using index(覆盖索引);

  5. 结合 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)添加索引。