Lihang Liu's Homepage

深入理解 B+ Tree

B+ 树的定义和 B 树是类似的,B+ 树在数据结构上进行了一定的改进,它的特性使得它的某些场景下教 B 树有了显著的优势。本文笔者将对 B+ 树的定义、特点以及应用进行详细解析。

定义

B+ 树和 B 树的定义是类似的,它们都是平衡多路搜索树,在文件系统和数据库系统中都有大量的应用。只不过 B+ 树有以下几点独特的属性:

  1. B+ 树的非叶子结点不包含值(value),只存储键(key)
  2. B+ 树的叶子结点之间是互相连接的,B+ 树叶子结点这一层构成了一个双向链表(Doubly linked list)
  3. B+ 树的叶子结点这一层存储了所有数据的 key-links pairs,而 B 树的所有数据是散布在整棵树中的,B 树的叶子结点这一层只存储了数据全集的一部分。

B+ 树的查找、添加、删除算法和 B 树是类似的,可以参考笔者的上一篇博客进行了解:深入理解 B-Tree | caffcen’s blog,在此不在赘述。

特性

对于阶数为 m 的 B+ 树,它拥有如下性质:

  1. 每个节点最多有 m 个子节点。
  2. 每个节点最多有 m-1 个键值。
  3. 每个内部节点至少有 ⌈m/2⌉ 个子节点,根节点除外。
  4. 根节点至少有 2 个子节点。
  5. 所有叶子节点出现在同一层级上,叶子结点与叶子结点之间有双向引用,叶子结点这一层构成了一条双向链表。
  6. 一个具有 k 个子节点的非叶节点包含 k-1 个键值。
  7. 叶子结点这一层存储了所有数据(所有的 key-link pairs)

B-Tree vs. B+ Tree

B 树和 B+ 树的区别对比,可以参考 Stack Overflow 这个问题:database - What are the differences between B trees and B+ trees? - Stack Overflow

B+ 树 B 树
非叶子节点数据存储情况 B+ 树非叶子结点不保存 value,只存储 key B 树所有结点都既保存 key 有存储 value
叶子节点结构 B+ 树叶子结点组成了一个 doubly-linked list B 树叶子结点之间是没有引用可以访问彼此的
叶子结点数据存储情况 B+ 树叶子结点包含了所有 key 的信息,及指向 key 所对应的 value 的指针 B 树叶子结点则只保存了部分 key-value 信息

B+ 树适用场景

B+ 树非常适用于做文件系统索引和数据库索引,这是因为:

  1. B+ 树的磁盘利用率更高
    • 这是因为 B+ 树内部节点仅存储 key,B 树内部节点既存储 key 又存储 value (link),导致同样大小的磁盘空间可以容纳的 B+ 树内部节点要比 B 树多。这也就意味着如果使用 B+ 树索引,相同的查询需要的 IO 次数更少。
  2. B+ 树支持高效的范围查询
    • 范围查询是非常常见的,因为 B+ 树叶子结点层构成了一个排好序的双向链表,因此对范围查询和整棵树的遍历是非常高效的。在 B 树结构中作范围查询的代价过高(类似在二叉搜索树中作范围查询,需要遍历整棵树),往往是无法接受的。
  3. B+ 树的查询效率是稳定且可预期的
    • B+ 树的搜索最终会在叶子结点这一层结束,因为内部节点只存储 key 不存储 value (link)。所以每一次搜索的时间开销是相近且可预测的。

B+ 树在 MySQL InnoDB 存储引擎中的使用

最后讲一讲 B+ 树在 InnoDB 存储引擎中的使用,这样对帮助理解 InnoDB 索引结构和 B+ 树具体是如何使用的有直观的帮助。

InnoDB 是索引组织的,表都是根据主键顺序存放的,InnoDB 的数据文件本身既是索引文件,底层数据结构就是 B+ 树。因此这种表也被称为索引组织表(Index organized table)。

B+ 树在实际使用中,阶数 m 一般都很大,使得 B+ 树具有了很高的扇出性(fanout),因此即便存储大量的数据,整棵树的高度一般都在 2~4 层,也就是说一次搜索只需要 2 到 4 次 IO。

在 MySQL InnoDB 中,索引可以分为聚集索引和辅助索引(非聚集索引)。

  • 聚集索引(clustered index)就是按照每张表的主键构造⼀棵 B+树,同时叶⼦节点中存放的即为整张表的⾏记录数据,也将聚集索引的叶⼦节点称为数据页。同 B+树数据结构⼀样,每个数据页都通过⼀个双向链表来进⾏链接。
  • 辅助索引(Secondary Index,也称⾮聚集索引),叶⼦节点并不包含⾏记录的全部数据。叶⼦节点除了包含键值以外,每个叶⼦节点中的索引⾏中还包含了⼀个书签(bookmark)。该书签⽤来告诉 InnoDB 存储引擎哪⾥可以找到与索引相对应的⾏数据。由于 InnoDB 存储引擎表是索引组织表,因此 InnoDB 存储引擎的辅助索引的书签就是相应⾏数据的聚集索引键(key)。

MySQL InnoDB 是索引组织表,每张表只能有一个主键、一个聚集索引文件,但是可以有多个辅助索引。

通过聚集索引进行查询,就是在 B+ 树中进行搜索;通过辅助索引进行查询,则需要进行两次查询:

  1. 第一次查询从辅助索引中找到该辅助索引键对应的主键是什么;
  2. 第二次查询用主键从聚集索引中进行查询。

如果读者对聚集索引和辅助索引还有疑惑,可以阅读《MySQL 技术内幕——InnoDB 存储引擎》的 5.4 和 4.1 节。这里就不多赘述了。

Reference

  1. 《MySQL 技术内幕——InnoDB 存储引擎》
  2. 《Algorithms, 4th Edition》by Robert Sedgewick, Kevin Wayne
  3. database - What are the differences between B trees and B+ trees? - Stack Overflow
  4. B+ tree - Wikipedia