一、什么是 B+ 树
B+ 树是一种为磁盘等外存储设备设计的多路平衡查找树,是 B 树的变体。它在数据库索引和文件系统中被广泛使用,核心目标是在海量数据下把查找所需的磁盘 I/O 次数降到最低。
理解 B+ 树,可以先抓住它与 B 树最本质的几点区别:
- 数据只存在叶子节点:非叶子节点只保存索引键(key)和指向子节点的指针,不保存实际数据行,因此单个节点能容纳更多的键,树的高度更低。
- 叶子节点之间用链表相连:所有叶子节点按 key 有序排列,并通过双向链表串联,天然支持范围查询和顺序遍历。
- 所有叶子节点在同一层:B+ 树是严格平衡的,从根到任意叶子的路径长度相同,查询性能稳定。
- 非叶子节点的 key 是子树的“路标”:一个 key 在非叶子节点中出现后,仍会出现在叶子节点中,非叶子节点只起导航作用。
1.1 结构示意
下面是一棵简化后的 B+ 树结构,非叶子节点只做路由,数据全部落在叶子层:

1.2 查找过程
以查找 key = 62 为例:
- 从根节点开始,62 大于 50 且小于 70,进入左边这个子树。
- 在内部节点 50
| 65中,62 大于 60,进入中间侧子节点。 - 到达叶子节点 60,62,在其中定位到 62。
整个过程只访问了 3 个节点,也就是 3 次磁盘 I/O。速度十分迅速,同时也能够最大程度上节省cpu的性能。
1.3 与 B 树的对比
下面是B树的照片:
| 对比项 | B 树 | B+ 树 |
|---|---|---|
| 数据存放位置 | 所有节点都可能存放数据 | 只有叶子节点存放数据 |
| 单节点可容纳的 key 数量 | 较少(数据占用空间) | 较多(只存 key 和指针) |
| 树的高度 | 相对更高 | 相对更低 |
| 范围查询 | 需要中序遍历,回溯较多 | 叶子链表顺序扫描,效率高 |
| 查询性能 | 命中位置不同,I/O 次数不稳定 | 每次都要走到叶子,I/O 次数稳定 |
二、MySQL 为什么使用 B+ 树
MySQL 的 InnoDB 存储引擎使用 B+ 树作为索引结构,这不是随意选择,而是由磁盘特性、数据规模和 SQL 查询模式共同决定的。
2.1 磁盘 I/O 是性能瓶颈
数据库的数据最终存放在磁盘上,磁盘随机读取的耗时远高于内存访问。一次磁盘寻道通常在毫秒级,而内存访问是纳秒级,两者相差几个数量级。因此索引结构的设计目标不是“比较次数最少”,而是磁盘 I/O 次数最少。
B+ 树的每个节点大小通常与磁盘页(InnoDB 默认 16KB)对齐,一次 I/O 就能读入一个完整节点。节点内使用二分查找,节点间通过多路分支降低树高,从而把 I/O 次数压到很低。但是B树非叶子节点也存储对应的数据会加大找寻数据的难度,树的高度也会变得更高一些,也就是会加大cpu的功耗。
2.2 非叶子节点不存数据,树更“矮胖”
假设 InnoDB 页大小为 16KB,主键为 BIGINT(8 字节),加上页内指针约 6 字节,一个非叶子节点大约能存放 16KB / 14B ≈ 1170 个 key。若每个叶子节点能存放约 16 条数据行,那么一棵高度为 3 的 B+ 树可以索引:
1170 × 1170 × 16 ≈ 2190 万行
也就是说,两千万级别的数据表,通过主键查询通常只需要 3 次磁盘 I/O。如果非叶子节点也存放数据行,单节点能容纳的 key 会大幅减少,树高随之增加,I/O 次数也会上升。
2.3 叶子链表天然支持范围查询
实际业务中,范围查询和排序非常常见,例如分页、时间区间筛选、ORDER BY、BETWEEN 等。B+ 树的叶子节点有序且相互连接,定位到起始位置后顺序扫描即可,避免了 B 树中频繁的中序回溯。
-- 定位到 100 后沿叶子链表顺序读取,效率很高
SELECT * FROM orders WHERE id BETWEEN 100 AND 200;
-- 按索引列排序时可直接利用叶子节点的有序性
SELECT * FROM orders ORDER BY id LIMIT 20;
2.4 查询性能稳定
B+ 树的所有数据都在叶子层,任何一次等值查询都必须走到叶子节点,路径长度一致。这意味着查询的时间复杂度稳定在 O(log n),不会出现某些 key 很快、某些 key 很慢的情况,便于容量评估和性能调优。
2.5 更适合全表扫描和顺序访问
由于数据全部集中在叶子层且按序排列,全表扫描或全索引扫描时只需顺序遍历叶子链表,磁盘预读(read-ahead)能发挥很好的效果,连续 I/O 的效率远高于随机 I/O。
2.6 与 InnoDB 的聚簇索引设计契合
InnoDB 的主键索引是聚簇索引,叶子节点直接存放整行数据;二级索引的叶子节点存放主键值,查询时再回表。这种设计正是建立在 B+ 树“数据只在叶子层”的前提之上:
- 聚簇索引:叶子节点即数据页,主键查询一次 B+ 树即可拿到整行。
- 二级索引:叶子节点存主键,先查二级索引再回表查聚簇索引,结构清晰、职责分明。
2.7 为什么不用其他结构
| 数据结构 | 主要问题 |
|---|---|
| 二叉搜索树 / AVL / 红黑树 | 每个节点只有两个分支,树高过大,磁盘 I/O 次数多 |
| B 树 | 非叶子节点存数据,扇出小、树更高,范围查询效率低 |
| 哈希表 | 只支持等值查询,不支持范围查询和排序,且无法顺序扫描 |
| 跳表 | 内存结构表现优秀,但磁盘上节点分散,I/O 不友好 |
| LSM 树 | 写性能好,但读放大明显,适合写多读少场景,与 OLTP 主键索引诉求不同 |
三、小结
B+ 树通过“非叶子节点只存索引、数据集中在叶子层、叶子节点链表相连”的设计,在磁盘 I/O 次数、范围查询能力和查询稳定性之间取得了很好的平衡。MySQL 选择 B+ 树,本质上是因为它最契合磁盘存储的特性与关系型数据库的查询模式:树足够矮,I/O 足够少,范围扫描足够快,性能足够稳定。
MySQL的InnoDB存储引擎采用B+树作为索引结构,主要因其能最小化磁盘I/O次数:非叶子节点仅存索引键,提升扇出度、降低树高;叶子节点有序链表天然支持高效范围查询与顺序扫描;所有查询路径长度一致,保障性能稳定;且与聚簇索引设计深度契合。相比B树,B+树在海量数据下的I/O效率、范围查询和全表扫描性能更优。

511

被折叠的 条评论
为什么被折叠?



