数据库索引的“王者之选”:为何 MySQL 独宠 B+ 树?

4 阅读7分钟

数据库索引的“王者之选”:为何 MySQL 独宠 B+ 树?

在数据库领域,索引是提升查询性能的“核武器”。而在 MySQL 的 InnoDB 存储引擎中,B+ 树(B+ Tree)无疑是这座武器库中的镇馆之宝。

你可能会问:哈希表(Hash Table)查询速度是 O(1),红黑树(Red-Black Tree)是经典的平衡二叉树,B 树(B-Tree)更是 B+ 树的“亲兄弟”,为什么 MySQL 偏偏选择了 B+ 树?

这并非偶然,而是由磁盘 I/O 特性数据检索场景以及系统稳定性共同决定的最优解。本文将深入剖析这场“数据结构选秀”,看看其他选手为何落败,而 B+ 树如何胜出。


一、淘汰赛第一轮:哈希表(Hash Table)

落败原因:不支持范围查询,无法应对排序

哈希表通过哈希函数将键(Key)直接映射到存储位置,其单点查询的时间复杂度确实是惊人的 O(1) ,比 B+ 树的 O(log N) 快得多。既然这么快,为什么不用它做主索引?

1. 致命缺陷:无法进行范围查询

数据库中最常见的操作不仅仅是 SELECT * FROM table WHERE id = 100,更多的是 SELECT * FROM table WHERE id > 100 AND id < 200

  • 哈希表的困境:哈希函数会将相邻的键(如 100, 101, 102)打散到完全不同的位置。要查范围,哈希表只能全表扫描,效率极低(O(N))。
  • B+ 树的优势:B+ 树的叶子节点通过双向链表连接,且数据有序排列。一旦定位到起始节点,只需沿着链表向后遍历即可,效率极高。

2. 无法利用索引排序

ORDER BY 是 SQL 中的高频操作。

  • 哈希表的困境:哈希后的数据是无序的,排序必须将所有数据加载到内存中进行堆排序或快排,消耗大量 CPU 和内存。
  • B+ 树的优势:天然有序,直接按顺序读取叶子节点即可得到有序结果,无需额外排序。

3. 哈希冲突与扩容成本

当数据量巨大时,哈希冲突不可避免,解决冲突(如链地址法)会降低性能。此外,当哈希表负载因子过高需要扩容(Rehash)时,可能需要迁移大量数据,导致数据库服务暂时不可用或抖动,这对在线系统是难以接受的。

结论:哈希表适合做等值查询的辅助索引(如 Redis),但不适合作为通用数据库的主索引结构。


二、淘汰赛第二轮:二叉查找树与红黑树

落败原因:树太高,磁盘 I/O 次数太多

红黑树是一种自平衡二叉查找树,保证了树的高度相对平衡。在内存计算中,它是完美的结构。但在数据库场景下,它有一个致命弱点:树太高了

1. 磁盘 I/O 是瓶颈

数据库的数据量通常远超内存,索引和数据主要存储在磁盘上。

  • 磁盘特性:磁盘读写是以“页”(Page,通常 4KB 或 16KB)为单位的。每次读取一个节点,就可能发生一次磁盘 I/O。I/O 耗时是内存访问的十万倍以上。
  • 红黑树的劣势:作为二叉树,每个节点最多只有 2 个子节点。假设存 100 万条数据,红黑树的高度约为 log2(106)20\log_2(10^6) \approx 20。这意味着最坏情况下,查询一条数据需要 20 次磁盘 I/O
  • B+ 树的优势:B+ 树是“多路平衡查找树”。一个节点可以包含成百上千个键值。假设一个节点能存 1000 个键,那么 100 万条数据的高度仅为 log1000(106)=2\log_{1000}(10^6) = 2。查询仅需 2-3 次 I/O

2. 局部性原理

红黑树的节点在磁盘上分布分散,难以利用预读机制。而 B+ 树的一个节点正好填满一个磁盘页,读取一次就能加载大量索引信息,完美契合磁盘的块设备特性。

结论:在内存中红黑树是王者,但在磁盘 I/O 主导的数据库世界,它太“瘦高”了,导致 I/O 次数过多,性能无法接受。


三、决赛圈对决:B 树 vs B+ 树

落败原因:B 树非叶子节点浪费空间,范围查询慢

B 树(B-Tree)和 B+ 树非常相似,都是多路平衡树。它们的区别在于数据存储的位置,正是这个细微差别决定了 B+ 树的胜利。

特性B 树 (B-Tree)B+ 树 (B+ Tree)胜出者
数据存储位置所有节点(根、中间、叶子)都存 Key + Data只有叶子节点存 Data,非叶子节点只存 KeyB+ 树
非叶子节点容量因存储 Data,占用空间大,单个节点存的 Key 少只存 Key,单个节点能存更多 Key,树更矮胖B+ 树
查询稳定性查到的数据可能在根节点,也可能在叶子,波动大所有查询都必须走到叶子节点,性能稳定B+ 树
范围查询需要中序遍历整棵树,逻辑复杂叶子节点有链表相连,直接线性扫描B+ 树

深度解析 B+ 树的两大杀手锏:

1. 更矮胖的树身 = 更少的 I/O

由于 B+ 树的非叶子节点不存储实际数据(Data),只存储索引键(Key)和指针,这使得单个磁盘页(Node)能容纳的索引项数量大幅增加。

  • 举例:假设一页能存 1KB 数据。

    • B 树:Key+Data 很大,一页可能只能存 50 个索引项。
    • B+ 树:只有 Key,一页可能存 500 个索引项。
    • 结果:同样的数据量,B+ 树的高度比 B 树更低,查询所需的磁盘 I/O 次数更少。
2. 完美的范围查询支持

B+ 树的所有叶子节点构成了一个有序的双向链表

  • 当执行 WHERE id BETWEEN 10 AND 100 时,B+ 树只需找到 10 所在的叶子节点,然后顺着链表向后读取直到 100 即可。
  • 而 B 树需要进行繁琐的中序遍历,且在非叶子节点命中数据后,还需要跳转回叶子节点继续找下一个,效率低下且实现复杂。
3. 查询性能稳定

在 B 树中,运气好时第一次 I/O 就找到了数据(在根节点),运气差时要遍历到叶子。这种不确定性对于数据库优化器来说是不利的。B+ 树强制所有查询都走到叶子节点,保证了稳定的查询延迟


四、总结:天作之合

MySQL 选择 B+ 树,是因为它在磁盘 I/O 效率范围查询能力数据有序性之间找到了完美的平衡点:

  1. 对比哈希:B+ 树支持范围查询和排序,适应性更广。
  2. 对比红黑树:B+ 树“矮胖”的多路结构极大减少了磁盘 I/O 次数。
  3. 对比 B 树:B+ 树非叶子节点纯索引化,进一步降低了树高,且叶子节点的链表结构让范围查询如虎添翼。

可以说,B+ 树是专门为**“磁盘存储 + 大量数据 + 混合查询(点查 + 范围查)”**这一数据库核心场景量身定制的数据结构。只要磁盘 I/O 依然是数据库的主要瓶颈,B+ 树的王者地位就难以撼动。

当然,随着 SSD 的普及和内存容量的爆炸式增长,未来可能会出现新的索引结构(如 LSM-Tree 在写多读少场景的崛起,或纯内存数据库使用的跳表),但在通用的关系型数据库领域,B+ 树依然是那个最可靠的守护者。