--- 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 执行计划解读]]