人人都会AI编程

11.2 EXPLAIN 执行计划详解

更新时间:2026-07-10

EXPLAIN 是 MySQL 提供给开发者最实用的性能诊断工具,没有之一。它不关心你的 SQL 写了多长、嵌套了多少层,只回答一句:“数据库准备怎么执行你这条语句?”EXPLAIN 放到任意 SELECTINSERTUPDATEDELETE 前执行,就能获得一张包含多个字段的执行计划表。读懂它,是区分“会写 SQL”和“能写好 SQL”的分水岭。

11.2.1 执行计划整体结构

一条 SQL 语句在 MySQL 中通常会被拆解成若干个基本步骤执行,如先扫描索引 A、再回表读取数据行、然后与另一张表的扫描结果做连接。EXPLAIN 输出的每一行,代表计划中的一个步骤,从上到下大致对应执行顺序。字段包括:

  • id:步骤编号
  • select_type:查询类型
  • table:操作的表名
  • partitions:匹配的分区
  • type:访问类型(最重要)
  • possible_keys:可能使用的索引
  • key:实际选择的索引
  • key_len:使用的索引长度
  • ref:索引比较的对象
  • rows:优化器估算的扫描行数
  • filtered:按条件过滤后的行数百分比
  • Extra:额外信息(非常重要)

下面逐一拆解每个字段,附带实战解读。

11.2.2 核心字段详解

id:步骤标识

  • 相同 id 表示从上到下顺序执行。
  • id 值越大,优先级越高,越先执行(常用于子查询)。
  • 对于 UNION 结果集合并,idNULL,表示临时表操作。

select_type:查询类型

表示每个步骤属于哪一类查询,常见值有:

  • SIMPLE:简单查询,不包含子查询或 UNION
  • PRIMARY:最外层查询。
  • SUBQUERYSELECT 中的子查询(不在 FROM 后)。
  • DERIVEDFROM 后的子查询(派生表)。
  • UNIONUNION 中的第二个或之后的查询。
  • UNION RESULTUNION 的合并结果。

这些信息帮助理解多表、多层查询的内部执行顺序。

type:访问类型(性能核心指标)

type 描述 MySQL 如何查找数据行,是判断 SQL 是否高效的核心指标。性能从优到劣大致排序为:

NULL > system > const > eq_ref > ref > range > index > ALL

理解每种类型的真实含义与出现条件至关重要:

  • NULL:优化阶段就能确定结果,无需访问表或索引。例如 SELECT 1; 或从常量表获取聚合函数值。
  • system:表只有一行数据(系统表),是 const 的特例,极少出现。
  • const:通过主键或唯一索引等值匹配一条记录,速度极快。例如 WHERE id = 1。查询在优化阶段即可完成大部分工作。
  • eq_ref:连接查询时,驱动表的每一行,在被驱动表中使用主键或唯一索引进行等值匹配,返回至多一行。这是联表查询中最好的访问方式。
  • ref:使用非唯一索引进行等值匹配,可能返回多行。例如 WHERE category_id = 10 且该列有索引。这是最常见的“好”访问类型。
  • range:索引范围扫描,如 ><BETWEENIN 等。性能尚可,但扫描行数通常多于 ref。注意,IN 如果值太多可能会退化为全表扫描。
  • index:全索引扫描,即扫描整个索引树。虽然它比 ALL(全表扫描)稍好(因为索引通常比数据文件小),但如果出现在核心查询中仍需警惕。常见于 ORDER BY 或覆盖索引的完整扫描。
  • ALL:全表扫描,逐行读取数据页,是性能最差的访问方式,必须尽可能避免。

possible_keyskey:候选索引与实际选择

  • possible_keys:优化器评估后认为可能会用到的索引列表。如果为 NULL,说明你的查询条件未能匹配任何索引,这是一个危险信号。
  • key:优化器最终实际选用的索引。如果 possible_keys 有值而 keyNULL,说明优化器发现走全表扫描成本更低,这种情况通常需要人工干预。

key_len:索引使用的长度

表示优化器实际使用了索引的多少字节。对于联合索引,key_len 可以明确反映出查询使用了索引的哪几列。例如,一个联合索引 (col1 INT, col2 VARCHAR(20)),如果条件中只用到 col1key_len 为 4;如果同时用到 col1col2 完全匹配,则 key_len 会大得多。该字段常用于验证“联合索引最左前缀”是否真正生效。

ref:索引比较的对象

显示索引值与什么进行比较,常见值有 const(常量)或另一个表的列。例如 WHERE t1.col = 'abc' 会显示 const;而连接条件如 t1.id = t2.user_id 会显示 db.t2.user_id

rows:优化器估算的扫描行数

这是优化器基于统计信息预估的每个步骤需要读取的行数。注意:它不是实际执行后的准确值,而是索引统计信息(innodb_stats_persistent)基础上的估算。 如果预估与实际差异巨大(例如千万行表却显示几十行),很可能是统计信息过期,需要执行 ANALYZE TABLE 更新。

filtered:过滤百分比

表示经过表条件过滤后,剩余满足条件的数据比例(百分比)。例如 rows=100filtered=10.00,意味着最终大约有 10 行参与下一步操作。这个字段在多表连接时尤其有用,能够反映驱动表过滤效果好坏。

Extra:额外关键信息(必须逐字阅读)

Extra 字段包含大量细节,对排查问题极为关键:

  • Using index覆盖索引,查询所需数据全部从索引中获取,无需回表。这是高性能查询的标志,应尽量追求。
  • Using where:表示 MySQL 在存储引擎返回数据后,还额外进行了 WHERE 条件过滤。通常出现在某些条件无法完全使用索引时,如范围查询后的额外 AND 条件。
  • Using index condition索引下推(ICP),将部分 WHERE 条件下推到引擎层,先过滤再回表,减少随机 I/O。MySQL 5.6+ 默认开启,这是非常有益的特性。
  • Using temporary:查询过程中需要创建临时表来存放中间结果,常见于 GROUP BYDISTINCTUNION 等操作。出现这个信息时要检查是否可以加索引避免临时表。
  • Using filesort:MySQL 需要进行额外的排序操作,无法利用索引的顺序直接返回结果。当 ORDER BY 不能借助索引有序性时出现。如果排序数据量大,会对性能造成较大影响,应尽量优化成 Using index 替代。
  • Using join buffer:连接时使用了连接缓冲(如 Block Nested Loop),通常意味着被驱动表没有合适的索引,每行驱动表数据都要做全表扫描。
  • Impossible WHEREWHERE 条件永远为假,优化器直接返回空结果。
  • No tables used:查询不涉及任何表。

11.2.3 实战解读示例

假设有两张表:

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    INDEX idx_name (name)
);

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2),
    INDEX idx_user (user_id)
);

执行以下查询并分析:

EXPLAIN
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.name = 'Alice';

执行计划可能如下:

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|----|-------------|-------|-------|---------------|----------|---------|-------|------|----------|-------------|
| 1 | SIMPLE | u | ref | idx_name | idx_name | 203 | const | 1 | 100.00 | Using where |
| 1 | SIMPLE | o | ref | idx_user | idx_user | 5 | db.u.id | 15 | 100.00 | NULL |

逐行解读:

  • 先查 users 表,使用 idx_name 索引通过 name='Alice' 等值匹配,type=ref,预估扫描 1 行,性能良好。
  • 然后将 u.id 传给 orders 表,orders 使用 idx_user 索引进行等值匹配,type=ref,预估每个用户有 15 个订单,性能同样不错。
  • 整体没有 Using filesortUsing temporary,查询质量高。

如果执行计划中出现 orderstype=ALLExtra 显示 Using join buffer,则说明 orders.user_id 可能没有索引(或未被使用),这时就需要检查索引是否存在。

11.2.4 常见风险执行计划与应对

  • type=ALL:全表扫描,立刻检查 possible_keys 是否为空,是否需要创建合适的索引。
  • Extra=Using filesort:检查 ORDER BY 的列是否在索引中,并且顺序符合最左前缀原则。
  • Extra=Using temporary:检查 GROUP BYDISTINCT 是否可以利用索引,或者 DISTINCTORDER BY 混合导致无法优化。
  • key_len 小于预期:联合索引只用了一半,条件列不符合最左前缀,往往需要调整查询条件顺序或索引列顺序。
  • rows 估算严重不准:执行 ANALYZE TABLE 更新统计信息,或者考虑调整 innodb_stats_persistent_sample_pages 采样页数。

EXPLAIN 并不是只能看一遍,对于慢查询,可以先 EXPLAIN 查看优化器的默认选择,然后尝试 EXPLAIN FORMAT=JSON 获取更多成本细节,或者用 EXPLAIN ANALYZE(MySQL 8.0.18+)实际执行并测量每一步的消耗时间,彻底锁定瓶颈。掌握这些字段的解读,你就拥有了诊断 SQL 性能的核心能力。