--- tags: [test/review, mysql, bplus-tree, clustered-index, secondary-index] create time: 2026-08-09 12:00 --- # B+树索引原理_测试题 ## 概述 本测试涵盖 InnoDB B+树索引的核心概念,包括 B 树与 B+树的本质差异、页分裂/合并机制、聚簇索引与非聚簇索引的区别,以及联合索引设计原则。共 10 道题:6 道选择题、3 道填空题、1 道简答题。 --- ## 一、选择题(6道,由浅入深) > **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景 ### Q1(基础)— 考察定义层面 InnoDB 存储引擎默认使用的索引数据结构是什么? A. 哈希表 B. B+树 C. 红黑树 D. 跳表 ### Q2(基础)→ 考察行为差异 以下关于 B 树和 B+树的说法中,正确的是哪一项? A. B 树的非叶子节点也存储数据记录 B. B+树的查询路径长度不一致,取决于数据位置 C. B 树的叶子节点通过链表连接,支持高效范围查询 D. B+树在同样大小的页中能容纳更多键,因此树更高 ### Q3(进阶)→ 考察原理理解 假设一个 InnoDB 表的页大小为 16KB,每条索引记录约 1KB,则单个页大约能容纳多少条索引键?在此条件下,1 亿行数据的 B+树高度通常是多少层? A. 约 16 条,树高 5~6 层 B. 约 16384 条,树高 3~4 层 C. 约 1024 条,树高 4~5 层 D. 约 100 条,树高 6~7 层 ### Q4(进阶)→ 考察比较辨析 为什么推荐使用自增 BIGINT 而非 UUID 作为主键?以下解释最准确的是: A. UUID 太长,导致 InnoDB 无法为其建立索引 B. 所有二级索引叶子节点都存储了主键值,UUID 乱序插入会导致频繁页分裂且二级索引体积庞大 C. UUID 不唯一,可能产生主键冲突 D. 自增主键的查询速度是 UUID 的两倍以上 ### Q5(深入)→ 考察场景推理 某业务表的主键为自增 BIGINT,当前填充率已达 85%。此时大量并发 INSERT 导致的页分裂策略是: A. 每次都是严格的二分平分 B. 首次分裂占 7/8,后续改为平分,以预留碎片空间 C. 只有根节点满时才向上分裂,子节点不会立即分裂 D. 先删除旧数据再合并,避免产生碎片 ### Q6(深入)→ 考察源码级细节 在二级索引的叶子节点中,实际存储的内容是: A. 完整行数据 B. 索引列的值 + 回滚指针(rollback pointer) C. 索引列的值 + 主键值 D. 索引列的值 + 该行的物理磁盘地址 --- ## 二、填空题(3道) ### F1 — 聚簇索引的定义 InnoDB 中被称为"聚簇索引"的实际上是______索引,也就是说数据和索引存储在同一个______中。一张表有且仅有______个聚簇索引。 > **提示**: 聚簇的含义指的是数据文件本身就是按 B+Tree 组织的一份索引结构,想一想哪个索引直接包含了整行数据。 ### F2 — 页分裂规则 当父节点也已满时,页分裂会______向上进行,直到找到不满的父节点或到达根节点。如果根节点发生分裂,则树的______会增加 1。 > **提示**: 分裂是从叶子节点向上传递的,根节点分裂是树结构变化的一个重要标志。 ### F3 — 联合索引设计 在设计复合索引 `(col1, col2, col3)` 时,区分度高的列应放在______;等值查询列应在范围查询列之______;若 `WHERE col1 = 'a' AND col2 > 10 AND col3 = 'b'`,则只有前______列能有效利用索引过滤。 > **提示**: 回忆最左前缀原则和范围查询断链陷阱——遇到范围查询时,后面的列就无法使用前缀匹配了。 --- ## 三、简答题(1道) ### S1 某电商订单表 `orders` 有以下字段:`id`(BIGINT 自增主键)、`user_id`、`status`、`created_at`、`amount`。现有以下两个高频查询: ```sql -- 查询 1:按用户查订单列表 SELECT id, status, created_at FROM orders WHERE user_id = 100 ORDER BY created_at DESC; -- 查询 2:统计各状态的订单总数 SELECT status, COUNT(*) FROM orders GROUP BY status; ``` 请综合分析: 1. 为这两个查询分别设计索引方案 2. 解释为什么要这样设计(考虑区分度、最左前缀、覆盖索引等因素) 3. 说明这种设计对 INSERT/UPDATE 操作可能带来的影响 > **答题框架提示**: 先分析每个查询的过滤条件和排序需求 → 判断哪些列适合做联合索引 → 评估是否覆盖所有 SELECT 列 → 考虑索引维护成本。 --- ## 参考答案与解析 ### 选择题答案 | 题号 | 正确答案 | 解析 | |------|---------|------| | Q1 | B | InnoDB 的默认索引结构是 B+树。哈希表不支持范围查询;红黑树是多路平衡树但在磁盘 IO 场景下不如多路树高效;跳表主要用于内存场景。 | | Q2 | A | B 树的每个节点都存储数据和键,而 B+树的非叶子节点只存键不做数据负担,所以 A 正确。B 错误因为 B+树所有查询路径等长;C 错误因为 B 树叶节点没有链表连接;D 错误因为 B+树同样数据量下树更矮而非更高。 | | Q3 | B | 16KB / 1KB ≈ 16 条是错误的估算方式——实际上索引键通常远小于 1KB,加上指向子节点的指针后每页可放下约 16000+ 个键。以每页约 1000~2000 个键计算,1 亿行数据的树高通常在 3~4 层。 | | Q4 | B | 核心原因有二:① UUID 长度 36 字节,远大于 BIGINT 的 8 字节,导致所有二级索引体积膨胀,内存缓存命中率下降;② UUID 随机性导致插入顺序无序,引发频繁的页分裂和碎片。A 错在 InnoDB 可以为 UUID 建索引;C 错在 UUID 设计保证全局唯一;D 无依据。 | | Q5 | B | InnoDB 采用"首次分裂占 7/8,后续平分"的策略来预留碎片空间,避免频繁分裂。这是面试常考的优化细节。A 错在不是严格二分;C、D 描述的策略不存在于 InnoDB 实现中。 | | Q6 | C | 二级索引叶子节点存储的是(索引列的值 + 主键值),通过主键可以回表到聚簇索引获取完整行。A 是聚簇索引叶子节点的内容;B 中的回滚指针用于 MVCC,不在二级索引中体现;D 的物理磁盘地址 InnoDB 不暴露给存储引擎外部。 | ### 填空题答案 | 题号 | 答案 | 解析 | |------|------|------| | F1 | 主键(或"聚簇");数据文件(或"InnoDB 表");1 | 聚簇索引的特点是数据文件和索引文件是同一份,叶子节点存储整行完整数据,每张表只能有一个聚簇索引。 | | F2 | 递归;高度 | 父节点满了要递归向上分裂,这是 B+树的经典再平衡操作。根节点分裂意味着需要新增一层,树高度 +1。 | | F3 | 前面;前;2 | 区分度高的列在前可以更早缩小查找范围;等值查询在前避免被范围查询中断最左前缀;`col1 = 'a'` 等值命中,`col2 > 10` 范围命中,但 `col3` 因 `col2` 的范围查询断链而无法使用索引。 | ### 简答题参考答案 **S1 参考答案要点**: 1. **查询 1 索引**:建议建立联合索引 `(user_id, created_at)`。`user_id` 是高区分度的等值过滤列,放前面可以快速定位;`created_at` 放后面可以直接满足 `ORDER BY` 排序需求,避免 filesort。同时 `id` 和 `status` 隐含在主键中,不需要额外覆盖。 2. **查询 2 索引**:建议建立 `(status)` 单列索引。`GROUP BY status` 可以利用索引的顺序分组,减少临时表的使用。虽然 `COUNT(*)` 需要从主键获取,但由于状态数通常很少(如待支付、已发货、已完成等),回表开销很小,不值得为这个查询单独建立覆盖索引。 3. **INSERT/UPDATE 影响**:增加索引会提高写入成本。每个 INSERT 需要在所有索引的 B+树中插入记录,可能导致页分裂。尤其是 `(user_id, created_at)` 联合索引,当 `created_at` 无序时也会触发分裂。可通过预排序批量插入或使用自增 ID + 顺序插入来缓解。 **评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖全部要点(索引设计 × 2 + 原因分析 × 1 + 写入影响 × 1)。 ## 关联笔记 - [[02.MySQL/index/B+树索引原理]]