Files
autumn-recruitment/02.MySQL/index/Explain 执行计划解读_test.md
T

216 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [test/review, mysql, explain, execution-plan, filesort]
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 列会出现什么警告信息?
```sql
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 字节计算
有一张表:
```sql
CREATE TABLE users (
name VARCHAR(64) NOT NULL,
age INT NOT NULL,
INDEX idx_name_age(name, age)
);
```
字符集 utf8mb4(每字符 4 字节)。当执行以下查询时:
```sql
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 语句原型:
```sql
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;
```
请综合分析:
1. 解释第一行(u 表)和第二行(o 表)中各字段的含义
2. 指出潜在的优化点
3. 说明为什么这里看不到 `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 参考答案要点**:
1. **第一行(users u 表)**:
- `type=ref`:通过非唯一索引 `idx_status` 进行等值查找(status = 1),匹配多行
- `key=idx_status`:实际使用了 status 索引
- `rows=500`:优化器估算扫描约 500 行
- `filtered=100%`:所有扫描的行都满足条件
- `Extra: Using index condition`:ICP 生效,在存储引擎层提前过滤
2. **第二行(orders o 表)**:
- `type=eq_ref`:对 u 表的每一行,通过 `o.user_id = u.id`(外键/主键)精确匹配一条订单记录
- `key=PRIMARY`:通过主键精确查找
- `rows=1`:每行精确匹配 1 条记录
- `Extra: Using where`:存储引擎返回数据后,Server 层再做 WHERE 过滤
3. **潜在优化点**:
- `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 是合理策略
4. **filesort 的隐含**:
- LIMIT 10 配合 ORDER BY 如果没有合适索引就需要 filesort
- EXPLAIN 的输出截断了 JOIN 之后的步骤,完整的执行流程还会检查是否需要额外排序
- 检查时应该看 `EXPLAIN ANALYZE`(MySQL 8.0.18+)或者观察执行时的 actual rows 来判断 filesort 是否发生
**评分标准**:逐行字段解释(×2 行各 2 分)+ 优化建议(2 分)+ filesort 分析(2 分)= 满分 8 分。答出关键点即可。
## 关联笔记
- [[02.MySQL/index/Explain 执行计划解读]]