欢迎来到窥视未来的博客

https://github.com/lwx57280 https://gitee.com/li_VillageHead

EXPLAIN 性能优化圣经:从 type 到 Extra,彻底读懂执行计划

EXPLAIN 性能优化圣经:从 type 到 Extra,彻底读懂执行计划

一条 SQL 为什么慢?为什么明明建了索引,查询还是快不起来?EXPLAIN 是 MySQL 提供的“体检报告”,它能告诉你优化器是如何决策的。本文用图解 + 实战案例,带你逐字段掌握 EXPLAIN 的核心要点,从此告别“盲猜式调优”。

 

一、为什么 EXPLAIN 是调优第一利器?

写出一句 SQL 很容易,但理解 MySQL 如何执行它,才是优化的起点。EXPLAIN 会输出执行计划——优化器基于统计信息和成本模型,为你的 SQL 选择的一条(或几条)路径。

通过执行计划,你可以回答:

  • 用到了哪些索引?实际用了吗?

  • 是全表扫描(ALL)还是索引扫描?

  • 有没有产生临时表(Using temporary)?

  • 有没有文件排序(Using filesort)?

本文使用一个示例表 user 来演示(主键 id,普通索引 idx_age,联合索引 idx_name_age)。所有 SQL 均可在 MySQL 5.7 / 8.0 上执行。

二、EXPLAIN 输出概览:一张表看懂所有列

EXPLAIN SELECT * FROM user WHERE age = 18 AND name = '张三';

输出可能如下(简化):

 
idselect_typetabletypepossible_keyskeykey_lenrefrowsfilteredExtra
1 SIMPLE user ref idx_name_age idx_name_age 152 const,const 2 100.00 NULL

下面逐一拆解这些列的含义。

三、type:连接类型的“性能金字塔”

type 表示 MySQL 如何查找表中的行,从最优到最差依次为:

system > const > eq_ref > ref > range > index > ALL
type含义示例场景好/差
system 只有一行数据的系统表(极少见) SELECT * FROM (SELECT 1) t ⭐⭐⭐⭐⭐
const 主键或唯一索引等值查询,最多匹配一行 WHERE id = 1 ⭐⭐⭐⭐⭐
eq_ref 连接查询中被驱动表的连接字段是主键或唯一索引 JOIN ... ON t1.id = t2.id ⭐⭐⭐⭐
ref 非唯一索引等值匹配 WHERE age = 18(age 有普通索引) ⭐⭐⭐⭐
range 索引范围扫描(> , < , BETWEEN , IN) WHERE id BETWEEN 1 AND 100 ⭐⭐⭐
index 全索引扫描(遍历整个索引树) SELECT age FROM user(age 有索引) ⭐⭐
ALL 全表扫描(最差) 无任何索引可用 ⭐

图解:金字塔越高,性能越好。

image

实战目标:至少让 type 达到 range 级别,争取 ref 或 const。如果看到 ALL,必须优化。

 

四、possible_keys, key, key_len:索引使用情况

  • possible_keys:优化器“可能”用到的索引列表。

  • key:实际使用的索引。如果为 NULL,则说明没有用索引。

  • key_len:实际使用的索引列长度(字节)。可以帮你判断联合索引具体使用了哪些列。

key_len 计算规则(重要,面试常考):

  • 字段类型 + 是否允许 NULL + 字符集。

  • 例如:INT (4字节) + NOT NULL → 4;允许 NULL → 5。

  • VARCHAR(255) 在 utf8mb4 下:最大长度 255×4=1020,加上变长标识 2 字节,再加 NULL 标志 1 字节 → 1023。

联合索引示例:索引 (name, age),name 为 VARCHAR(100) utf8mb4,age 为 INT。

    • 若 key_len 为 407:说明只用到了 name(100×4 + 2 + 1 = 403,加上age的4?这里计算仅是示意)。
      实际需要根据字段精确计算,但你只需要知道:key_len 越大,代表用上的索引列越多。

五、rows 与 filtered:扫描估算与过滤效率

  • rows:优化器预计需要读取的行数(不是返回的行数)。这个值越小越好。

  • filtered:存储引擎层过滤后剩余的比例(百分比)。例如 rows=1000, filtered=10.00,说明大约会返回 100 行。主要用于连接查询中估算被驱动表要读多少行。

六、Extra:关键信息速查表

Extra 列提供额外信息,往往直接揭示性能瓶颈。

 
Extra 值含义建议
Using index 覆盖索引(只读索引,不回表)🎉 最佳状态,保持
Using where 存储引擎层返回后,Server 层还要过滤(常见于索引不能完全覆盖条件) 可尝试覆盖索引或调整索引
Using index condition 索引下推(ICP),部分条件在索引层过滤了 5.6 后默认开启,好现象
Using filesort 需要额外排序操作(无法利用索引直接有序)⚠️ 优化:让 ORDER BY 走到索引
Using temporary 使用了临时表(常见于 GROUP BY 或 DISTINCT 无索引)⚠️ 优化:为分组列加索引
Impossible WHERE WHERE 条件永远为假(如 1=2) 检查逻辑
Select tables optimized away 极少数情况,比如只有一行时聚合 无需关心

重点警告:一旦出现 Using filesort 或 Using temporary,必须优化!它们通常意味着需要磁盘 I/O。

七、实战案例:一步一步优化慢 SQL

场景:订单表 orders(百万级),查询 2024 年 1 月 1 日之后某用户的总金额。

SELECT user_id, SUM(amount) 
FROM orders 
WHERE user_id = 12345 
  AND create_time > '2024-01-01' 
GROUP BY user_id;

1. 初始 EXPLAIN

索引:仅在 create_time 上有索引

type: range
possible_keys: idx_create_time
key: idx_create_time
Extra: Using where; Using temporary; Using filesort

问题:Using temporary; Using filesort 说明分组排序都用临时表和文件排序,性能极差。

2. 优化方案

创建联合索引:(user_id, create_time),并让 user_id 在前。

ALTER TABLE orders ADD INDEX idx_user_time (user_id, create_time);

3. 优化后 EXPLAIN

type: ref
key: idx_user_time
Extra: Using index condition

type 从 range 提升到 ref;Extra 中的 Using temporary 消失,因为 GROUP BY user_id 可以直接使用索引排序。

 

4. 进一步覆盖索引

如果查询只涉及 user_id 和 amount,可以再加 amount 到索引中,变成 (user_id, create_time, amount),实现 Using index,避免回表。

八、扩展工具 

  • EXPLAIN FORMAT=JSON:输出结构化 JSON,可查看更详细的成本信息。

  • EXPLAIN ANALYZE(MySQL 8.0.18+):实际执行并输出运行时间、循环次数等,非常强大。

  • optimizer_trace:开启跟踪,看优化器如何比较不同执行方案

SET optimizer_trace='enabled=on';
SELECT * FROM user WHERE age = 18;
SELECT * FROM information_schema.optimizer_trace\G

九、总结:EXPLAIN 快速诊断口诀

 
现象怎么办
type 是 ALL 加索引
key 是 NULL 检查索引失效场景
Extra 有 Using filesort 给 ORDER BY 列加索引
Extra 有 Using temporary 给 GROUP BY / DISTINCT 列加索引
Extra 有 Using index 优秀,继续保持
rows 远大于预期 检查索引的离散度,或者统计信息过旧(ANALYZE TABLE)

 

记住:执行计划是通往 SQL 优化的唯一地图。每天花十分钟分析业务中的慢查询,不出一个月,你就是团队里的 SQL 调优专家。

如果觉得本文对你有帮助,欢迎点赞、收藏、转发!
关注我,获取更多 MySQL 实战干货。

posted on 2026-05-15 18:06  k8s-Mango  阅读(73)  评论(0)    收藏  举报

导航