Explain 执行计划详解:SQL 性能瓶颈与索引命中分析
文章目录
- 《Explain 执行计划详解:SQL 性能瓶颈与索引命中分析》
- 一、前言:为什么要用 Explain
- 二、Explain 基本语法
- 三、type 字段:判断访问效率
- 示例对比:
- 四、key 与 possible_keys:索引使用情况
- 示例:
- 五、rows 与 filtered:扫描代价估算
- 六、Extra 字段:隐藏的性能信号
- 七、Explain 优化思路(五步法)
- ① 看 type(访问方式)
- ② 看 key(实际索引)
- ③ 看 rows(扫描行数)
- ④ 看 Extra(执行细节)
- ⑤ 验证 SQL 改写效果
- 八、实际案例:索引优化前后对比
- 表结构:
- 优化前:
- 优化后:
- 九、面试高频问题与答题模板
- 十、总结
《Explain 执行计划详解:SQL 性能瓶颈与索引命中分析》
一、前言:为什么要用 Explain
大家好,我是程序员卷卷狗。
在 MySQL 中,SQL 的执行效率取决于优化器的决策。
优化器会根据表结构、索引、统计信息,选择一条“最优路径”去执行查询。
我们可以用 EXPLAIN 查看优化器选择的执行方案,
从而判断:
- SQL 是否使用索引;
- 是否走全表扫描;
- 哪个表先被连接;
- 过滤行数预估是多少。
一句话:
EXPLAIN 是读懂 SQL 性能瓶颈的第一步。
二、Explain 基本语法
EXPLAIN SELECT * FROM user WHERE age > 18;
或更详细版本:
EXPLAIN FORMAT=JSON SELECT * FROM user WHERE age > 18;
常见输出字段
| 字段名 | 含义 |
|---|---|
| id | 查询执行顺序(越大越先执行) |
| select_type | 查询类型(简单查询/子查询/联合查询) |
| table | 当前访问的表 |
| partitions | 使用的分区(若有) |
| type | 连接类型(访问方式) |
| possible_keys | 可能用到的索引 |
| key | 实际使用的索引 |
| key_len | 索引长度 |
| ref | 索引匹配的列 |
| rows | 预估扫描行数 |
| filtered | 过滤比例 |
| Extra | 额外信息(最关键) |
三、type 字段:判断访问效率
type 表示 MySQL 如何访问数据,是最重要的指标。
| 值(从优到劣) | 含义 | 示例 |
|---|---|---|
| system | 只有一行数据 | 特殊情况 |
| const | 主键或唯一索引等值查询 | WHERE id=1 |
| eq_ref | 联表时主键匹配 | JOIN 场景 |
| ref | 非唯一索引等值匹配 | WHERE name='卷卷狗' |
| range | 范围扫描 | WHERE age>20 |
| index | 全索引扫描 | 只访问索引,不访问表 |
| ALL | 全表扫描 | 最低效 |
记忆口诀:
system → const → eq_ref → ref → range → index → ALL
→ 越靠前越好。
示例对比:
EXPLAIN SELECT * FROM user WHERE id=1;
-- type: const ✅
EXPLAIN SELECT * FROM user WHERE age>20;
-- type: range ⚠️
EXPLAIN SELECT * FROM user;
-- type: ALL ❌
四、key 与 possible_keys:索引使用情况
| 字段 | 说明 |
|---|---|
| possible_keys | 优化器认为“可能可用”的索引 |
| key | 实际被使用的索引 |
示例:
EXPLAIN SELECT * FROM user WHERE name='卷卷狗';
输出:
possible_keys: idx_name
key: idx_name
→ 索引命中成功。
若 key 为空:
possible_keys: idx_name
key: NULL
→ 索引未被使用,需检查:
- 是否类型不匹配;
- 是否函数作用在列上;
- 是否联合索引失效。
五、rows 与 filtered:扫描代价估算
| 字段 | 含义 |
|---|---|
| rows | 预估扫描行数(越少越好) |
| filtered | 过滤比例(0~100%) |
例如:
rows: 10000
filtered: 10
表示优化器预估会扫描 1 万行,其中 10% 能通过过滤。
关键启示:
如果 rows 太大,说明索引选择性差,需要优化。
六、Extra 字段:隐藏的性能信号
Extra 提供额外优化信息,
是判断是否“真正走索引”的关键。
| Extra | 含义 | 说明 |
|---|---|---|
| Using index | 覆盖索引(不回表) ✅ | |
| Using index condition | 索引下推(ICP 优化) ✅ | |
| Using where | 使用 where 过滤(可能未走索引) ⚠️ | |
| Using temporary | 使用临时表(排序/分组代价大) ❌ | |
| Using filesort | 文件排序(未命中索引排序) ❌ | |
| Using join buffer | JOIN 时使用缓存(无索引) ⚠️ | |
| Impossible where | WHERE 条件恒 false | |
| Using MRR | 多范围读取(优化 IO) ✅ |
出现 “Using index” 是性能最好的信号。
“Using filesort” 通常是性能瓶颈的标志。
七、Explain 优化思路(五步法)
① 看 type(访问方式)
- 是否为
const、ref或range; - 若为
ALL,说明未走索引。
② 看 key(实际索引)
- 检查是否命中正确索引;
- 若空,考虑索引设计或条件改写。
③ 看 rows(扫描行数)
- 行数大说明过滤不精准;
- 可通过添加索引或优化条件减少扫描量。
④ 看 Extra(执行细节)
- 避免
Using filesort、Using temporary; - 确保出现
Using index或Using index condition。
⑤ 验证 SQL 改写效果
- 使用
ANALYZE TABLE更新统计信息; - 再次
EXPLAIN观察优化效果。
八、实际案例:索引优化前后对比
表结构:
CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(50),age INT,INDEX idx_name_age (name, age)
);
优化前:
EXPLAIN SELECT * FROM user WHERE age>20;
输出:
type: ALL
rows: 100000
Extra: Using where
→ 未走索引,全表扫描。
优化后:
EXPLAIN SELECT * FROM user WHERE name='卷卷狗' AND age>20;
输出:
type: range
key: idx_name_age
rows: 120
Extra: Using index condition
→ 命中联合索引,性能大幅提升。
九、面试高频问题与答题模板
| 问题 | 答案要点 |
|---|---|
| Q1:EXPLAIN 的作用是什么? | 查看 SQL 执行计划,分析是否走索引。 |
| Q2:type 字段代表什么? | 表示访问方式,从 const 到 ALL 性能递减。 |
| Q3:possible_keys 与 key 的区别? | 前者是可用索引,后者是实际使用索引。 |
| Q4:rows 越大说明什么? | 扫描行数多,索引不精准。 |
| Q5:Extra 中哪些是性能警告? | Using filesort、Using temporary。 |
| Q6:如何判断走了覆盖索引? | Extra 出现 Using index。 |
| Q7:如何改写 SQL 提升 type 等级? | 优化索引结构、避免函数、改写 WHERE 顺序。 |
十、总结
EXPLAIN 是 SQL 优化的“放大镜”,
它能揭示每条语句的执行细节与瓶颈所在。
一句话记住:
看 type 判路径,看 key 判命中,看 Extra 判代价。
掌握这三个核心指标,就能判断 SQL 是否真正高效。
下一篇(第 16 篇),我将写——
《MySQL 慢查询优化:从定位、分析到索引调优的完整流程》,
手把手讲清楚如何使用 slow_query_log、EXPLAIN、优化重写 SQL。
