2. 执行计划入门:EXPLAIN 命令详解

执行计划是数据库优化器为 SQL 语句生成的执行步骤描述。通过 EXPLAIN 命令,我们可以查看 MySQL 如何访问表、使用哪些索引、预估扫描多少行数据。掌握执行计划的解读能力,是慢查询优化的第一步,也是最关键的一步。

2.1 EXPLAIN 基本用法

在任意 SELECT 语句前加上 EXPLAIN 关键字,即可获取该查询的执行计划。MySQL 8.0 还支持 EXPLAIN FORMAT=JSON 和 EXPLAIN ANALYZE 两种增强格式。

-- 基础用法
EXPLAIN SELECT * FROM orders WHERE order_id = 10086;

-- JSON 格式(包含成本估算)
EXPLAIN FORMAT=JSON
SELECT * FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.user_id = 12345;

-- 真实执行计划(MySQL 8.0.18+)
EXPLAIN ANALYZE
SELECT * FROM orders WHERE status = 'PAID' AND create_time > '2024-01-01';
实践建议:日常排查慢查询时,优先使用 EXPLAIN 快速定位索引问题;需要精确分析耗时分布时,使用 EXPLAIN ANALYZE 获取实际执行时间与循环次数。

2.2 执行计划输出列详解

EXPLAIN 输出包含 12 个列(MySQL 8.0),其中 type、rows、Extra 是判断查询性能的核心指标。下表列出所有列及其含义:

列名 含义 优化关注度
id 查询中 SELECT 的标识符,id 越大越先执行 中
select_type 查询类型:SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION 等 中
table 输出行所引用的表名 低
type 访问类型:从全表扫描到唯一索引查找,性能依次提升 高
possible_keys 查询可能使用的索引列表 中
key 实际选择的索引 高
key_len 使用的索引字节长度,可判断联合索引使用了几个字段 高
ref 索引查找时使用的列或常量 中
rows 优化器预估需要扫描的行数 高
filtered 经过表条件过滤后剩余行数的百分比 中
Extra 额外信息:Using index、Using filesort、Using temporary 等 高

2.3 type 列:访问类型全解析

type 列是衡量查询效率最直观的指标,从最优到最差排列如下:

  1. system:表只有一行(系统表),const 的特例,几乎见不到。
  2. const:通过主键或唯一索引等值查找,最多返回一行。速度极快。
  3. eq_ref:连接查询中,被驱动表通过主键或唯一索引等值匹配,每行只匹配一条。
  4. ref:通过普通索引等值查找,可能返回多行。
  5. fulltext:使用全文索引进行匹配。
  6. ref_or_null:ref 的基础上额外查找 NULL 值。
  7. index_merge:使用多个索引合并后访问(交集/并集)。
  8. unique_subquery:IN 子查询中使用主键或唯一索引。
  9. index_subquery:IN 子查询中使用普通索引。
  10. range:索引范围扫描,如 BETWEEN、>、<、IN 列表。
  11. index:全索引扫描,遍历整个索引树,比全表扫描略快。
  12. ALL:全表扫描,最差情况,应极力避免。
优化红线:生产环境中,type 为 ALL 或 index 的查询必须优化。目标是将核心查询的 type 提升到 ref 及以上,至少达到 range。

2.4 rows 列:预估扫描行数的意义

rows 是优化器根据索引统计信息估算的需要扫描的行数,并非精确值。它反映了查询的"工作量":

  • rows 越小越好:通常与查询响应时间正相关。
  • 注意误差:当表数据频繁更新或统计信息过期时,rows 可能严重偏离实际值。此时可执行 ANALYZE TABLE 更新统计信息。
  • 多表关联:rows 是驱动表的扫描行数,被驱动表的扫描次数由驱动表行数决定。
-- 示例:rows 从 500 万降到 1 的优化效果
-- 优化前:type=ALL, rows=5234567
EXPLAIN SELECT * FROM orders WHERE order_no = 'ORD20240101001';

-- 优化后:type=const, rows=1(添加唯一索引 idx_order_no)
EXPLAIN SELECT * FROM orders WHERE order_no = 'ORD20240101001';

2.5 Extra 列:关键信息解读

Extra 列包含优化器执行策略的补充信息,以下是最常见的几类:

Extra 信息 含义 优化建议
Using index 覆盖索引,查询所需字段全部在索引中,无需回表 ✅ 理想状态,尽量保持
Using where 在存储引擎层过滤后,还需在 Server 层过滤 通常正常,若结合 Using filesort 需关注
Using index condition 索引条件下推(ICP),将部分 WHERE 条件下推到存储引擎 ✅ 好现象,减少回表次数
Using filesort 无法利用索引排序,需要额外排序操作 ⚠️ 应优化,尝试建立排序字段的索引
Using temporary 使用了临时表(常见于 GROUP BY、DISTINCT、子查询) ⚠️ 应优化,考虑索引或改写 SQL
Using join buffer 连接时未使用索引,使用了连接缓冲区 ❌ 必须优化,为连接字段添加索引
Impossible WHERE WHERE 条件永远为假 检查业务逻辑或参数
No tables used 查询没有 FROM 子句(如 SELECT 1+1) 无需关注
重点关注:当 Extra 中出现 Using filesort 或 Using temporary 时,意味着查询无法完全利用索引完成排序或分组,通常伴随较大的内存/磁盘开销,是慢查询的常见原因。

2.6 执行计划综合解读流程

解读执行计划时,建议按以下步骤逐一排查:

  1. 看 type:是否为 ALL 或 index?若是,立即考虑添加索引。
  2. 看 key:实际使用的索引是否与 possible_keys 一致?若不一致,检查索引选择偏差原因。
  3. 看 rows:预估扫描行数是否在可接受范围内?多表关联时关注驱动表的 rows。
  4. 看 Extra:是否存在 Using filesort、Using temporary、Using join buffer?这些是性能瓶颈信号。
  5. 看 key_len:联合索引是否用到了所有字段?key_len 较短说明只使用了前缀部分。
EXPLAIN 执行计划解读决策树 获取 EXPLAIN 输出 type 是否为 ALL 或 index? ⚠️ 必须添加索引 ✅ 继续检查 Extra Extra 含 filesort/temporary? ⚠️ 优化排序/分组索引 检查 rows 是否过大 rows 远大于实际返回行数? 执行 ANALYZE TABLE ✅ 执行计划基本健康

2.7 实战案例:从执行计划到优化

以下是一个真实慢查询的优化过程:

-- 原始慢查询(响应时间 3.2s)
SELECT order_id, user_id, status, total_amount
FROM orders
WHERE status = 'PAID'
ORDER BY create_time DESC
LIMIT 20;

-- EXPLAIN 输出
-- type: ALL(全表扫描)
-- rows: 1,245,678
-- Extra: Using where; Using filesort

问题分析:

  • type=ALL:全表扫描 124 万行。
  • Extra=Using filesort:无法利用索引排序,需要额外排序。

优化方案:建立联合索引 (status, create_time),同时覆盖 WHERE 过滤和 ORDER BY 排序。

-- 添加联合索引
ALTER TABLE orders ADD INDEX idx_status_create_time (status, create_time);

-- 优化后 EXPLAIN
-- type: ref
-- key: idx_status_create_time
-- rows: 15,234(仅扫描 PAID 状态的行)
-- Extra: Using index condition(索引条件下推,无需 filesort)

优化后查询响应时间从 3.2s 降至 12ms,性能提升超过 260 倍。

核心原则:执行计划是 SQL 优化的"体检报告"。每次上线新查询前,务必执行 EXPLAIN 确认 type 达到 ref 或 range,Extra 不出现 filesort 和 temporary。养成这个习惯,能避免 90% 以上的线上慢查询问题。