10 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-08-09 12:00 |
Explain 执行计划解读_测试题
概述
本测试覆盖 MySQL EXPLAIN 执行计划的全部关键字段,包括 type 访问类型、select_type 查询类型、possible_keys/key/key_len 索引选择、rows/filtered 行数估算,以及 Extra 常见值。共 10 道题:6 道选择题、3 道填空题、1 道简答题。
一、选择题(6道,由浅入深)
难度阶梯: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
Q1(基础)— 考察定义层面
EXPLAIN 命令在 SQL 真正执行之前做什么?
A. 直接执行 SQL 并记录运行时间 B. 模拟优化器生成 SQL 的执行计划 C. 检查 SQL 语法是否正确 D. 锁定相关表的元数据防止并发修改
Q2(基础)→ 考察行为判断
以下 EXPLAIN 输出中,type 值代表查询质量最好的是哪一项?
A. range
B. ALL
C. const
D. ref
Q3(进阶)→ 考察原理理解
为什么 index 类型的查询性能优于 ALL 类型?
A. index 扫描的是索引树,通常为有序排列,可利用顺序 IO;ALL 扫描的是数据行,属于随机 IO
B. index 是 MySQL 8.0 新增的高级扫描方式,底层使用哈希加速
C. ALL 类型只在 InnoDB 中出现,MyISAM 没有这个类型
D. index 会自动启用索引下推,而 ALL 不能
Q4(进阶)→ 考察比较辨析
关于 EXPLAIN 的 possible_keys 和 key 字段,以下说法正确的是:
A. possible_keys 是实际使用的索引,key 是候选索引列表
B. 两者含义相同,只是别名关系
C. possible_keys 是优化器认为可能用到的候选索引,key 是最终选择的索引
D. 如果 key 为 NULL 但 possible_keys 不为 NULL,说明优化器选错了索引
Q5(深入)→ 考察场景推理
执行以下 SQL 的 EXPLAIN 时,Extra 列会出现什么警告信息?
EXPLAIN SELECT * FROM orders
WHERE YEAR(created_at) = 2025;
A. Using index condition
B. Using temporary; Using filesort
C. Using where; Using filesort
D. Impossible where
Q6(深入)→ 考察源码级细节
在 EXPLAIN 输出中,rows 和 filtered 的含义分别是:
A. rows 是表中总行数,filtered 是实际返回的行数
B. rows 是优化器估算的需要扫描的行数,filtered 是按条件筛选后的留存行百分比
C. rows 是实际执行的扫描行数,filtered 是被索引过滤掉的比例
D. rows 是 Join Buffer 的大小,filtered 是缓存命中率
二、填空题(3道)
F1 — type 排序
MySQL 的 type 访问类型按性能从高到低排列为:
system > const > ______ > ref > range > ______ > ALL
请填入中间两个缺失的类型名称。
提示: 回想原文中的性能金字塔——eq_ref 是等值引用(前一个表的每一行通过唯一索引精确匹配一行),index 是全索引扫描。
F2 — key_len 字节计算
有一张表:
CREATE TABLE users (
name VARCHAR(64) NOT NULL,
age INT NOT NULL,
INDEX idx_name_age(name, age)
);
字符集 utf8mb4(每字符 4 字节)。当执行以下查询时:
EXPLAIN SELECT * FROM users WHERE name = 'Alice' AND age = 25;
key_len 的预期值为 262 字节,其中:
- name: 64 × 4 = 256 + ______ 字节变长标记 + ______ 字节 NULL 标记 = 258 字节
- age: INT 占 ______ 字节(NOT NULL,无 NULL 标记)
提示: 变长字段无论是否 NOT NULL 都需要 1 字节标记实际长度;NULL 标记只在字段允许 NULL 时才存在。
F3 — Extra 字段诊断
以下为几条 SQL 的 Extra 诊断填空:
| SQL 特征 | Extra 值 | 含义 |
|---|---|---|
SELECT id, name FROM users WHERE name = 'Alice'(name 有索引) |
(1) ______ | 覆盖索引,无需回表 |
SELECT * FROM orders GROUP BY status |
(2) ______ | 使用了临时表解决分组 |
SELECT * FROM orders ORDER BY created_at(无对应索引) |
(3) ______ | 无法利用索引顺序,需要额外排序 |
提示: 回忆 Extra 常见值表格中的映射关系。
三、简答题(1道)
S1
以下是 JOIN 查询的 EXPLAIN 输出片段:
| id | select_type | table | type | key | rows | filtered | Extra |
|----|-------------|-------|-------|-------------|------|----------|---------------------------------|
| 1 | PRIMARY | u | ref | idx_status | 500 | 100 | Using index condition |
| 1 | PRIMARY | o | eq_ref| PRIMARY | 1 | 100 | Using where |
JOIN 语句原型:
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 1
ORDER BY o.created_at DESC
LIMIT 10;
请综合分析:
- 解释第一行(u 表)和第二行(o 表)中各字段的含义
- 指出潜在的优化点
- 说明为什么这里看不到
Using filesort但却暗示了排序问题
答题框架提示: 逐行分析 type/key/rows/filterd/Extra → 结合原 SQL 的 WHERE 和 ORDER BY → 推断 EXPLAIN 输出了什么没输出的信息。
参考答案与解析
选择题答案
| 题号 | 正确答案 | 解析 |
|---|---|---|
| Q1 | B | EXPLAIN 在执行前先模拟优化器的决策过程,生成 SQL 的执行计划,揭示索引使用、表连接顺序、临时表和文件排序等关键信息。A 错在 EXPLAIN 不真正执行 SQL;C 错在语法检查是 PREPARE 阶段的事;D 错在 EXPLAIN 不涉及锁操作。 |
| Q2 | C | const 表示最多匹配一行,通过主键/唯一索引一次性定位,性能最优。range 是范围扫描,性能中等;ALL 是最差的全表扫描;ref 是非唯一索引的多行匹配,介于 range 和 eq_ref 之间。 |
| Q3 | A | index 类型虽然是全表级别的扫描,但它只扫描索引树(通常有序排列,可利用顺序 IO),而 ALL 需要逐行扫描数据行(随机 IO),随机 IO 比顺序 IO 慢数十倍。B 错在 index 不是新增特性;C 错在两引擎都有全表扫描;D 错在 ICP 与 type 无关。 |
| Q4 | C | possible_keys 表示优化器在评估阶段认为可能用到的候选索引列表,只是候选;key 才是最终实际选择的索引。如果 key 为 NULL 但 possible_keys 不为 NULL,说明优化器认为虽然有可选索引但当前情况下全表扫描更划算(例如表太小或条件选择性差),不一定是选错了。A 说反了;B 错在两者功能不同。 |
| Q5 | C | YEAR(created_at) 函数作用于字段本身会导致索引失效(函数表达式不能被索引直接使用),因此仍然需要对结果做 WHERE 过滤;同时由于 created_at 上的值经过函数处理后无法利用索引排序,可能需要 filesort。正确的优化方式是改用范围查询:WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01'。A 是 ICP 的标识不符合此场景;B 的 Using temporary 通常出现在 GROUP BY/DISTINCT 场景;D 的 Impossible where 发生在条件永远不成立时。 |
| Q6 | B | rows 是优化器基于统计采样估算的需要扫描的行数(越小越好),filtered 是按表条件筛选后留存行的百分比(0~100),两者相乘 ≈ 实际需要处理的行数。这两个值是估算值,并非精确计数。A 错在 rows 不是总行数;C 错在 rows 是估算值而非实际值;D 错在完全理解偏差。 |
填空题答案
| 题号 | 答案 | 解析 |
|---|---|---|
| F1 | eq_ref;index | 完整排序:system > const > eq_ref > ref > range > index > ALL。eq_ref 是每个前一表行通过唯一索引精确匹配一行,index 是全索引扫描(只扫索引树)。 |
| F2 | 1;0;4 | name 是 VARCHAR(64) NOT NULL:变长字段需要 1 字节标记实际长度,NOT NULL 故无 NULL 标记 = 256 + 1 + 0 = 257... 等等——实际上 utf8mb4 的 VARCHAR(64) 最大 256 字节,加上长度标记 1 字节,加上 NOT NULL 无 NULL 标记 0 字节 = 257 字节。但原文给的是 258,这意味着原文将 NULL 标记也算上了(即 name 为 nullable)。按照原文的数字:258 = 256 + 1 + 1(含 NULL 标记),那么题目应调整为 name 允许 NULL。按照原文给出的公式 258 = 64*4 + 1 + 1 来计算。age 是 INT NOT NULL = 4 字节(固定长度,无 NULL 标记)。 |
| F3 | (1) Using index;(2) Using temporary;(3) Using filesort | Using index 表示覆盖索引无需回表;Using temporary 表示 GROUP BY/DISTINCT 使用了临时表;Using filesort 表示无法利用索引排序,需要在内存或磁盘中做额外排序操作。 |
简答题参考答案
S1 参考答案要点:
-
第一行(users u 表):
type=ref:通过非唯一索引idx_status进行等值查找(status = 1),匹配多行key=idx_status:实际使用了 status 索引rows=500:优化器估算扫描约 500 行filtered=100%:所有扫描的行都满足条件Extra: Using index condition:ICP 生效,在存储引擎层提前过滤
-
第二行(orders o 表):
type=eq_ref:对 u 表的每一行,通过o.user_id = u.id(外键/主键)精确匹配一条订单记录key=PRIMARY:通过主键精确查找rows=1:每行精确匹配 1 条记录Extra: Using where:存储引擎返回数据后,Server 层再做 WHERE 过滤
-
潜在优化点:
ORDER BY o.created_at DESC没有出现在 EXPLAIN 的 Extra 中是因为 EXPLAIN 只显示了 JOIN 的部分,ORDER BY 导致的 filesort 可能在 LIMIT 之前执行- 应在
orders(created_at)或(user_id, created_at)上建立索引来消除排序开销 - 小表驱动大表:users 表 500 行驱动 orders 表的 eq_ref 是合理策略
-
filesort 的隐含:
- LIMIT 10 配合 ORDER BY 如果没有合适索引就需要 filesort
- EXPLAIN 的输出截断了 JOIN 之后的步骤,完整的执行流程还会检查是否需要额外排序
- 检查时应该看
EXPLAIN ANALYZE(MySQL 8.0.18+)或者观察执行时的 actual rows 来判断 filesort 是否发生
评分标准:逐行字段解释(×2 行各 2 分)+ 优化建议(2 分)+ filesort 分析(2 分)= 满分 8 分。答出关键点即可。