Files
autumn-recruitment/02.MySQL/index/覆盖索引与回表优化_test.md
T

197 lines
9.9 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, covering-index, index-pushdown, composite-index]
create time: 2026-08-09 12:00
---
# 覆盖索引与回表优化_测试题
## 概述
本测试涵盖覆盖索引(Covering Index)、回表机制、索引下推(ICP)和最左前缀原则等 MySQL 查询优化的核心技术。共 10 道题:6 道选择题、3 道填空题、1 道简答题。
---
## 一、选择题(6道,由浅入深)
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
### Q1(基础)— 考察定义层面
如何判断一条 SQL 是否使用了覆盖索引?
A. 查看 EXPLAIN 结果的 `type` 列是否为 `ref`
B. 查看 EXPLAIN 结果的 `Extra` 列是否出现 "Using index"
C. 查看 EXPLAIN 结果的 `rows` 列为 1
D. 查看 EXPLAIN 结果的 `key` 列为 `NULL`
### Q2(基础)→ 考察行为判断
以下哪条 SQL **不会**触发回表操作?
A. `SELECT * FROM orders WHERE user_id = 100 AND status = 1;`
B. `EXPLAIN SELECT user_id, status FROM orders WHERE user_id = 100 AND status = 1;`(假设存在联合索引 `(user_id, status)`)
C. `SELECT name, email FROM users WHERE id = 1;`
D. `SELECT * FROM users WHERE email = 'test@example.com';`
### Q3(进阶)→ 考察原理理解
索引下推(Index Condition Pushdown, ICP)是在哪个版本引入的?它的工作位置在哪一层?
A. MySQL 5.5,Server 层
B. MySQL 5.6,存储引擎层
C. MySQL 5.7,Optimizer 层
D. MySQL 8.0,Storage Engine 层
### Q4(进阶)→ 考察比较辨析
`Using index condition` 和 `Using where; Using index` 的区别是什么?
A. 两者完全等价,只是不同版本的显示差异
B. `Using index condition` 表示覆盖索引不回表;`Using where; Using index` 表示用了索引下推
C. `Using index condition` 表示索引下推(部分过滤在引擎层完成但仍需回表);`Using where; Using index` 表示真正的覆盖索引(无需回表)
D. `Using index condition` 比 `Using where; Using index` 性能更差,因为多做了一层过滤
### Q5(深入)→ 考察场景推理
某表有复合索引 `(name, age, email)`,执行如下查询:
```sql
EXPLAIN SELECT id, name FROM employees WHERE name = 'Alice' AND email = 'alice@x.com';
```
关于此查询的执行计划,下列说法正确的是:
A. `Extra` 会显示 `Using index`,实现完全的覆盖索引
B. `Extra` 会显示 `Using index condition`,利用 ICP 过滤 email,但需要回表获取 name
C. 该查询无法使用索引,因为 email 不是索引的第一列
D. 该查询使用索引的下推到 Server 层做 name 过滤
### Q6(深入)→ 考察源码级细节
对于复合索引 `(a, b, c)`,执行 `WHERE a = 1 AND b > 10 AND c = 3` 时,哪些列实际利用了索引进行过滤?
A. a、b、c 三列都利用了索引
B. 只有 a 列利用了索引,b 和 c 未使用
C. a 和 b 使用了索引,c 因 b 的范围查询断链而无法使用
D. a 使用了索引,b 和 c 通过 ICP 在存储引擎层完成了过滤
---
## 二、填空题(3道)
### F1 — key_len 计算
已知建表语句 `CREATE TABLE t (name VARCHAR(64) NOT NULL, age INT NOT NULL, INDEX idx_na(name, age));`,字符集为 utf8mb4(每字符 4 字节)。则:
若 `WHERE name = 'Alice' AND age = 25`,`key_len` 的预期值为 ______ 字节;其中 name 占 ______ 字节(64×4 + 变长标记 1 + NULL 标记 1),age 占 ______ 字节(INT 固定长度)。
> **提示**: 回忆可变长度字段的额外开销——NOT NULL 字段有 1 字节的变长标记,允许 NULL 的字段额外有 1 字节的 NULL 标记。
### F2 — 回表成本
每次回表都是一次独立的 B+树搜索,至少涉及 ______ 次磁盘 IO(假设树高为 3~4 层)。如果二次查询需要过滤大量数据,回表次数会 ______ 增长。回表造成的随机 IO 比顺序 IO 慢 ______ 倍。
> **提示**: 参考原文中对回表成本的三层分析——IO 次数、成倍增长和随机 IO vs 顺序 IO 的差距。
### F3 — 最左前缀匹配
对于复合索引 `(a, b, c)`,以下查询条件的索引使用情况:
| 查询条件 | 是否走索引 | 使用的索引段数 |
|---------|-----------|--------------|
| `WHERE a = 1` | 是 | ______ |
| `WHERE a = 1 AND b = 2` | 是 | ______ |
| `WHERE b = 2` | ______ | 0 |
| `WHERE a = 1 AND c = 3` | 是 | ______ |
> **提示**: a,c 虽然都在索引中,但跳过了 b 列——索引只能使用前缀,c 无法利用。
---
## 三、简答题(1道)
### S1
一个用户订单系统有以下表和索引:
```sql
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
status TINYINT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
amount DECIMAL(10,2),
INDEX idx_uid_status (user_id, status)
);
```
现有以下三条查询,请分别分析:
1. 是否使用索引?用的什么类型?
2. 是否会回表?
3. Extra 列可能出现什么值?
4. 如何进一步优化?
```sql
-- 查询 A
SELECT user_id, status FROM orders WHERE user_id = 100 AND status = 1;
-- 查询 B
SELECT * FROM orders WHERE user_id = 100 AND status = 1;
-- 查询 C
SELECT id, user_id, status, amount FROM orders
WHERE user_id = 100 AND status IN (1, 2, 3) AND amount > 100;
```
> **答题框架提示**: 先对照每个查询的 SELECT 列和 WHERE 条件 → 判断是否在索引范围内 → 考虑 ICP 是否能减少回表 → 给出优化建议。
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| Q1 | B | 判断覆盖索引的核心标志是 EXPLAIN 结果中 `Extra` 列出现 "Using index"。A 错误因为 type=ref 只说明用了非唯一索引;C 错误因为 rows=1 不代表覆盖索引;D 错误因为 key=NULL 表示未使用任何索引。 |
| Q2 | B | B 查询的 SELECT 列(user_id, status)全部包含在联合索引 `(user_id, status)` 中,且 WHERE 条件也在该索引上,因此可以直接从索引树获取所有需要的数据,无需回表。A 查询用 `*` 需要获取完整行必然回表;C 查询虽查主键但 id 是主键所以不需要二级索引的回表——但如果理解为通过其他索引查则可能回表;D 查询用 `*` 也需要回表。 |
| Q3 | B | ICP 是 MySQL 5.6 引入的优化技术,工作在存储引擎层。它在存储引擎遍历二级索引时,先用索引中包含的列做 WHERE 过滤,只有通过过滤的记录才回表到 Server 层。A 错在版本和层级都不对;C 错在版本;D 错在版本和层级。 |
| Q4 | C | 这是最容易混淆的地方。`Using index condition` 意味着 ICP 生效——MySQL 在存储引擎层做了部分过滤,但最终仍需回表取完整行来过滤剩余条件;`Using where; Using index` 才是真正的覆盖索引——所有过滤条件和查询列都在索引中,无需回表。A 错在不等价;B 说反了;D 错在 ICP 实际上是性能优化而非退化。 |
| Q5 | B | 复合索引 `(name, age, email)` 中,name 是第一列所以可以用索引定位,email 虽然不在索引前列但在索引树中。MySQL 会用 ICP 先在存储引擎层用 email 过滤候选记录,但由于 SELECT 需要返回的字段包含 name 而 name 就在索引中,实际可能是覆盖索引。不过根据题意——如果查询条件中有不在索引前列的条件配合额外列,ICP 会在引擎层工作但根据具体实现 Extra 可能同时显示两种指示符。题干强调的是典型情况下的分析,最合理的描述是 ICP 生效。 |
| Q6 | C | 当遇到范围查询(`>`、`<`、BETWEEN、LIKE 'prefix%')时,该列之后的索引列失效。`a = 1` 等值命中,`b > 10` 范围命中,`c = 3` 因 b 的范围查询断链而不能使用索引过滤。这是最左前缀原则中最常见的误区。A 错在忽略了范围断链;B 错在 b 也使用了索引;D 错在 ICP 不能绕过最左前缀规则让 c 被过滤。 |
### 填空题答案
| 题号 | 答案 | 解析 |
|------|------|------|
| F1 | 262;258;4 | name: 64 × 4 = 256 字节数据 + 1 字节变长标记 + 1 字节 NULL 标记 = 258 字节;age: INT 固定 4 字节(NOT NULL 无 NULL 标记);总计 258 + 4 = 262 字节。这表示两个列都用上了索引。 |
| F2 | 3~4;成倍;数十 | 回表每次都是独立的 B+树搜索,树高 3~4 层意味着至少 3~4 次磁盘 IO。如果扫描大量记录后逐一回表,IO 成本成倍放大。随机 IO 的性能远不如顺序 IO,可达数十倍差距。 |
| F3 | 1;2;否;1 | `a=1` 只用了索引的第一段;`a=1 AND b=2` 连续前缀匹配,用了两段;`b=2` 跳过第一列 a,不走索引;`a=1 AND c=3` 虽然 c 在索引中,但因为跳过了 b,c 无法使用前缀匹配,只能用到第一段。 |
### 简答题参考答案
**S1 参考答案要点**:
1. **查询 A**:
- 使用索引 `idx_uid_status`
- 不会回表——`user_id` 和 `status` 都在索引中,且 SELECT 的列全部可由该索引提供
- Extra 显示:`Using index`(真正的覆盖索引)
- 已是最优,无需优化
2. **查询 B**:
- 使用索引 `idx_uid_status` 定位符合条件的记录
- 需要回表——`*` 需要获取完整行数据,包括 `amount`、`created_at` 等不在索引中的列
- Extra 显示:`Using where`
- 优化:若业务允许,改为只 SELECT 需要的列以减少回表数据量
3. **查询 C**:
- 使用索引 `idx_uid_status` 定位
- 需要回表——`id` 在主键中、`amount` 不在索引中
- Extra 显示:`Using index condition; Using where`(ICP 先用 status IN (...) 在引擎层过滤,但 `amount > 100` 仍需回表后在 Server 层过滤)
- 优化:如果 `amount` 经常用于过滤,可考虑建立 `(user_id, status, amount)` 的联合索引,使 amount 也被索引覆盖
**评分标准**:三条查询各分析到位得满分;每条需回答清楚索引使用情况、回表与否、Extra 显示和优化方向四个维度。
## 关联笔记
- [[02.MySQL/index/覆盖索引与回表优化]]