Skip to content

B+ 树

为什么需要B+ 树

B 树虽然已经大幅降低了磁盘 I/O 次数,但在实际数据库系统中仍有两个痛点:范围查询效率低(需要中序遍历整棵树)、数据分散在所有节点(缓存命中率不理想)。

B+ 树正是为解决这些问题而设计的变体。它把所有数据都下沉到叶子节点,并用链表将叶子串联起来,从而天然支持高效的顺序访问和范围查询。这也是几乎所有关系型数据库(MySQL InnoDB、PostgreSQL)选择 B+ 树作为索引结构的根本原因。408 考研中,B+ 树与 B 树的对比是高频考点。

核心思想

B+ 树的核心特点:

  • 所有数据存储在叶子节点:内部节点仅保存索引(关键字副本),不存放实际数据记录
  • 叶子节点通过指针依次链接:形成一个有序链表,支持顺序遍历
  • 查找必须走到叶子:无论目标是否在内部节点出现,都要一路查到叶子才能获取数据

B+ 树的结构示意:

         [30 | 60]              ← 内部节点(仅索引)
        /    |    \
   [10|20] [30|50] [60|80]      ← 内部节点(仅索引)
    / | \   / | \   / | \
  [叶] → [叶] → [叶] → [叶]    ← 叶子节点(存数据,链表相连)

交互可视化

通过下方的交互动画,你可以逐步观察B+ 树的执行过程:

加载可视化中...

操作详解

B+ 树的结构

一棵 m 阶 B+ 树满足以下性质:

  1. 每个内部节点最多有 m 棵子树(m 个指针)
  2. 根节点至少有 2 棵子树(除非整棵树只有一个叶子)
  3. 除根外的内部节点至少有 ⌈m/2⌉ 棵子树
  4. 内部节点的关键字个数 = 子树个数(注意:B 树中关键字个数 = 子树个数 - 1)
  5. 所有叶子节点在同一层,包含全部关键字及指向数据记录的指针
  6. 叶子节点之间通过顺序指针链接成有序链表

关键步骤(查找过程):

  1. 从根节点开始,在当前节点的关键字中确定搜索方向
  2. 沿指针向下进入子节点,重复比较
  3. 必须到达叶子节点才能确认关键字是否存在并获取数据
  4. 若在叶子中找到目标关键字,查找成功;否则查找失败

B 树 vs B+ 树

对比项B 树B+ 树
数据存储位置所有节点都存数据仅叶子节点存数据
内部节点作用既是索引又存数据纯索引,不存数据
关键字个数n 个关键字对应 n+1 棵子树n 个关键字对应 n 棵子树
关键字是否重复关键字不重复内部节点的关键字会在叶子中再次出现
查找路径可能在任意层命中必须查到叶子层
查找性能不稳定(最好 O(1),最坏 O(log n))稳定(每次都是 O(log n))
叶子节点链表有,叶子按顺序链接
范围查询需中序遍历,效率低沿叶子链表扫描,效率高
内部节点扇出较小(节点同时存数据,占空间)较大(纯索引,单节点容纳更多关键字)

数据库索引应用

数据库选择 B+ 树而非 B 树作为索引结构,原因如下:

  1. 磁盘 I/O 更少:内部节点不存数据,同样大小的磁盘页可以容纳更多关键字,树的高度更低,磁盘读取次数更少
  2. 范围查询高效SELECT * FROM t WHERE id BETWEEN 10 AND 50 只需定位到叶子节点 10,然后沿链表顺序扫描到 50 即可
  3. 查询性能稳定:每次查询都走到叶子,路径长度一致,响应时间可预测
  4. 全表扫描友好:遍历所有叶子节点的链表即可完成全表顺序扫描,无需遍历整棵树

在 MySQL InnoDB 中,聚簇索引的叶子节点直接存放整行数据,非聚簇索引的叶子节点存放主键值(需要回表查询)。

复杂度分析

操作时间复杂度说明
查找O(log n)每次都从根到叶,路径长度稳定
插入O(log n)找到叶子后插入,可能引发分裂向上传播
删除O(log n)找到叶子后删除,可能引发合并向上传播
范围查询O(log n + k)定位起点 O(log n),沿链表扫描 k 个结果

空间复杂度:O(n),所有关键字存储在叶子节点中,内部节点额外存储索引副本。

考研高频考点

  • ⭐ B+ 树与 B 树的区别(选择题/简答题必考,重点记忆对比表)
  • ⭐ B+ 树查找必须到叶子节点(判断题高频陷阱)
  • ⭐ B+ 树中内部节点关键字个数 = 子树个数(与 B 树不同,易混淆)
  • ⭐ 为什么数据库索引使用 B+ 树(简答题,从磁盘 I/O、范围查询、稳定性角度回答)
  • 叶子节点链表的作用(范围查询、顺序遍历)
  • m 阶 B+ 树非根内部节点的关键字数范围:⌈m/2⌉ ~ m