一、EXPLAIN概述

EXPLAIN是MySQL中用于分析SQL执行计划的核心工具,它能模拟优化器执行SQL查询语句,帮助我们理解MySQL如何处理SQL,从而定位性能瓶颈。

为什么需要EXPLAIN?

在没有EXPLAIN之前,我们排查SQL性能问题往往靠"猜":是不是索引没建?是不是数据量太大?这种盲目尝试不仅效率低,还可能引入新问题(比如乱加索引导致写性能下降)。EXPLAIN的价值在于 “透明化MySQL的执行逻辑” ,解决以下工作痛点:

  • 判断索引是否生效
  • 定位全表扫描场景
  • 优化连接查询顺序
  • 分析临时表与排序问题

二、EXPLAIN执行计划核心字段详解

字段(Column) 含义说明 关键取值 / 常见现象 性能影响 优化建议与分析方法
id 查询中每个 SELECT 的唯一标识 相同 id:同一查询块;
不同 id:子查询/UNION;
id 越大越先执行
多 id 表示查询结构复杂,可能影响可读性与效率 简化嵌套子查询,尽量用 JOIN 替代;避免深层派生表(<derivedN>
select_type SELECT 的类型 SIMPLE(简单查询)
SUBQUERY / DERIVED(子查询/派生表)
PRIMARY(最外层)
DERIVED 会物化为临时表,开销大;多层子查询易导致性能下降 将子查询改写为 JOIN;避免 FROM (SELECT ...) 派生表,或确保其内部高效
table 当前操作的表名 实际表名 或 <derivedN>(派生表) 派生表无法使用原表索引,需额外物化 对派生表内部查询加索引;或提前物化为临时表手动管理
partitions 访问的分区(若启用) 具体分区名 或 NULL 若未分区剪枝(扫描所有分区),等效于全表扫描 确保 WHERE 条件包含分区键,触发分区剪枝
type 访问类型(最重要指标之一) 从优到劣:consteq_refrefrangeindexALL ALL = 全表扫描 ❌(大表致命)index = 全索引扫描 ref/range = 良好 避免 ALL:为 WHERE/JOIN 字段加索引;
争取 refrange
复合索引提升选择性
possible_keys 可能使用的索引 列出候选索引 或 NULL NULL 表示无可用索引 → 必走全表扫描 检查 WHERE、JOIN、ORDER BY 字段是否建了索引
key 实际使用的索引 具体索引名 或 NULL NULL = 未用索引,即使 possible_keys 有值也可能因统计不准而放弃 执行 ANALYZE TABLE 更新统计信息;
FORCE INDEX 测试索引效果;
调整索引设计(如复合索引)
key_len 实际使用索引的字节数 数值(如 4, 768) 可判断复合索引用到第几列数值过小 → 索引未充分利用 根据 key_len 反推索引使用深度;
调整索引列顺序(高选择性字段靠前)
ref 与索引比较的列或常量 const(常量)db.table.col(JOIN 关联列) 显示是否通过索引等值匹配 确保 JOIN 条件字段有索引;
避免隐式类型转换(会导致 refALL
rows 估算需扫描的行数 数值(如 1000, 500000) 数值越大,I/O 和 CPU 开销越高若远大于返回行数 → 过滤效率低 结合 filtered 判断;
添加覆盖更多 WHERE 条件的复合索引;
避免低选择性字段单独建索引
filtered Server 层过滤后保留比例(%) 如 10.00 = 保留 10% 值越低,说明大量数据被“筛掉”,索引未覆盖过滤条件 将高过滤性的 WHERE 字段加入索引,提升 filtered
Extra 补充执行细节(极其关键!) Using index ✅(覆盖索引)
Using where(正常)
Using index condition ✅(ICP 优化)
Using filesort ❌(需排序)
Using temporary ❌(建临时表)
Using temporary; Using filesort 是性能杀手组合 ⚠️ ORDER BY / GROUP BY 字段建索引;
使用覆盖索引避免回表;
避免 SELECT *,只查必要字段
  • type访问类型解释
    • const / system:通过主键或唯一索引一次命中一行(如 WHERE id = 1)✅ 极快
    • eq_ref:多表 JOIN 时,使用主键或唯一索引关联 ✅ 高效
    • ref:使用非唯一索引查找(如 WHERE status = 'active' 且 status 有索引)✅ 良好
    • range:索引范围扫描(如 BETWEEN, IN, >)✅ 可接受
    • index:全索引扫描(遍历整个索引树)⚠️ 比全表快,但仍有开销
    • ALL:全表扫描 ❌ 最差!应尽量避免(尤其大表)

EXPLAIN 结果准确性判断(快速 checklist)

问题 检查方法 解决方案
估算行数(rows)严重偏离实际? 对比 EXPLAIN vs EXPLAIN ANALYZE(MySQL 8.0+) 执行 ANALYZE TABLE table_name; 更新统计信息
明明有索引却没用? 查看 key=NULLpossible_keys 非空 检查数据分布(如某值占比 >20% 时优化器可能放弃索引)
执行计划与预期不符? SHOW WARNINGS 查看重写后的 SQL 避免函数操作字段(如 WHERE YEAR(create_time)=2025),改用范围查询
生产环境慢,但 EXPLAIN 看起来 OK? 结合慢日志 + Performance Schema 排查锁等待、磁盘 IO、并发瓶颈等非执行计划因素

一句话总结
看 type 避全表,盯 Extra 防临时,查 key 确索引,估 rows 控成本

三、常见执行计划类型与优化建议

问题类型 EXPLAIN 关键表现 根本原因分析 优化建议
全表扫描(Full Table Scan) type = ALL
key = NULL
rows 接近全表行数
查询条件未命中任何索引;或索引存在但因数据分布、选择性差等原因被优化器忽略 1. 为 WHERE 条件列创建合适索引
2. 避免在索引列上使用函数或表达式
3. 检查是否因隐式类型转换导致索引失效
索引失效 type = ALLindex尽管 possible_keys 非空 字符串值未加引号(如 WHERE name = abc);
在索引列上使用函数(如 WHERE YEAR(create_time) = 2023);
隐式类型转换(如数字列用字符串查询);
使用 !=NOT INLIKE '%xxx' 等破坏索引顺序的操作
1. 严格遵循数据类型书写规范
2. 将函数移至常量侧(如 create_time >= '2023-01-01'
3. 对前缀模糊查询考虑全文索引或冗余字段
未使用覆盖索引 Extra = Using where(而非 Using index 查询的 SELECT 列不在索引中,需回表读取聚簇索引(主键)获取完整行数据 1. 构建包含所有查询字段的复合索引(覆盖索引)
2. 评估是否可减少 SELECT 字段数量以适配现有索引
临时表(Temporary Table) Extra = Using temporary GROUP BY / DISTINCT / 子查询等操作无法通过索引完成分组或去重,需中间存储结果 1. 为 GROUP BY 或 DISTINCT 的字段建立索引
2. 调整 SQL 结构(如改子查询为 JOIN)
3. 增大 tmp_table_sizemax_heap_table_size(治标)
文件排序(Filesort) Extra = Using filesort ORDER BY 字段无有效索引,或排序字段与索引顺序不一致,或混合 ASC/DESC 导致无法利用索引排序 1. 为 ORDER BY 字段创建索引(注意顺序和方向)
2. 避免在排序字段上使用函数
3. 若同时有 WHERE 和 ORDER BY,优先构建 (WHERE列, ORDER BY列) 复合索引
连接缓冲(Join Buffer) Extra = Using join buffer (Block Nested Loop) JOIN 关联字段无索引,MySQL 使用嵌套循环 + 内存缓冲进行连接 1. 为 JOIN 的 ON 条件字段添加索引
2. 确保驱动表选择合理(小结果集驱动大表)
3. 避免笛卡尔积

补充说明:

  • type 字段是判断访问效率的核心指标:理想情况应达到 refeq_refrange,避免 ALL 或低效的 index
  • Extra 字段是优化线索的关键来源:重点关注 Using temporaryUsing filesort,它们通常意味着性能瓶颈。
  • 复合索引设计需结合查询模式:遵循“最左前缀原则”,并优先覆盖高频查询的 WHERE + ORDER BY + SELECT 字段。

四、MySQL 8.0执行计划新特性

1. EXPLAIN FORMAT=JSON

提供更详细的执行计划,包含JSON结构化数据:

EXPLAIN FORMAT=JSON SELECT * FROM employees WHERE department_id = 10;

2. 树状执行计划(format=tree)

从MySQL 8.0.16开始支持,更直观地展示查询逻辑:

EXPLAIN FORMAT=tree SELECT * FROM employees WHERE department_id = 10;

输出包含:

  • actual time:实际执行时间
  • actual rows:实际扫描行数
  • loops:循环次数

3. EXPLAIN ANALYZE

实际执行SQL并显示执行时间,但需注意会执行SQL,对于写操作需谨慎:

EXPLAIN ANALYZE SELECT * FROM employees WHERE department_id = 10;

五、总结

EXPLAIN是MySQL查询优化的基石,掌握其分析方法至关重要。
以下是关键要点总结:

  1. 核心关注点:重点关注typekeykey_lenExtra字段
  2. 优化目标:确保type至少达到range级别,优先使用refeq_ref
  3. 索引优化:合理设计索引,避免索引失效,善用覆盖索引
  4. 性能指标rows越小越好,Extra中避免Using temporaryUsing filesort
  5. MySQL 8.0:利用新特性如EXPLAIN FORMAT=JSONEXPLAIN ANALYZE进行更精准的分析

六、面试问题与答案

问题1:为什么有时EXPLAIN的rows估计值不准确?如何解决?

答案
rows是基于统计信息估算的,表数据变化频繁或统计信息过期会导致估算不准。解决方法是执行ANALYZE TABLE更新统计信息。

深层原理:MySQL优化器基于表的统计信息(如行数、索引分布)进行查询计划选择。当数据频繁更新而统计信息未更新时,优化器可能基于过时信息选择低效的执行计划。ANALYZE TABLE会重新收集表的统计信息,让优化器做出更准确的判断。

问题2:当使用EXPLAIN查看执行计划时,发现Extra显示"Using filesort",应该如何优化?

答案
"Using filesort"表示需要额外的排序操作,无法通过索引完成。优化方法有:

  1. 为ORDER BY子句的字段创建索引
  2. 如果排序字段在索引中,考虑使用覆盖索引
  3. 避免在排序字段上使用函数或表达式
  4. 考虑减少排序的行数,例如通过WHERE条件过滤

深层原理:当MySQL无法使用索引进行排序时,需要在内存或磁盘上进行额外的排序操作。排序操作的时间复杂度为O(n log n),对于大数据量查询会显著影响性能。创建适当的索引可以避免这个额外的排序步骤,提高查询效率。

问题3:Extra 中出现 Using indexUsing where; Using index 有何不同?

答案

  • Using index:查询所需的所有列都包含在索引中(覆盖索引),无需回表,且 WHERE 条件也由索引满足。
  • Using where; Using index:虽然用了覆盖索引(不回表),但部分过滤条件仍需在 Server 层处理(比如函数、非索引列比较)。
  • 两者都不回表,但前者更高效。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐