B+ 树页分裂和页合并是怎么发生的?
简化版
B+ 树插入导致叶子页放不下时,会把页拆成两个页,并把分隔 key 插入父节点;如果父节点也满了,分裂会向上传播,甚至让根分裂、树高加 1。删除后页太空时,会先尝试向兄弟页借 key,借不到就合并页,并从父节点删除分隔 key,合并也可能向上传播。
详细版
B+ 树以页为单位存储节点。页分裂发生在插入后节点超过容量时:叶子页分裂时,数据项在两个叶子之间重新分布,并把新页的最小 key 复制到父节点作为导航 key;内部页分裂时,中间 key 通常上推到父节点,左右两边成为两个内部节点。这个差异是高频细节。
页合并发生在删除后节点低于最低占用率时。数据库或教材实现通常先尝试和相邻兄弟重分布,如果兄弟也不富余,再合并两个页,并更新父节点分隔 key。分裂和合并的目的不是让节点“刚好一半”,而是保持节点容量约束、搜索有序性和树高平衡。
完整版教学
一、页为什么会分裂
B+ 树节点通常对应磁盘页或内存页,每页容量有限。假设一个叶子页最多能放 4 个 key,当前页是 [10,20,30,40],再插入 50 就放不下了。此时不能简单扩容当前页,因为页大小固定;B+ 树的做法是分裂成两个叶子页,并把新页接入叶子链表。
before insert 50:
[10,20,30,40]
after split:
[10,20,30] -> [40,50]
分裂的本质是把一个溢出的页拆成两个合法页,同时让父节点知道右侧新页从哪个 key 开始。
二、叶子页分裂为什么是 copy up
B+ 树叶子页存放真实数据项,父节点只做导航。因此叶子页分裂后,父节点中插入的是右侧新叶子的最小 key 的副本,而这个 key 仍然保留在叶子页中。这个过程常叫 copy up。
leaf split:
leaf1: [10,20,30]
leaf2: [40,50]
parent receives separator: 40
parent key 40 是导航副本,leaf2 里仍然有 40
这和普通 B 树或 B+ 树内部节点分裂的“中间 key 上推”不同。叶子层必须保留完整数据,因为所有查询最终都要在叶子层命中数据项。
三、内部页分裂为什么可能向上传播
如果父节点插入分隔 key 后也满了,父节点也要分裂。内部页分裂时,中间 key 通常被推到更上层,左右两边分别成为两个内部页。若根也分裂,就创建新根,树高增加 1。
| 分裂位置 | 父节点拿到什么 | key 是否保留在原节点 |
|---|---|---|
| 叶子页分裂 | 右叶子最小 key 的副本 | 保留在叶子页 |
| 内部页分裂 | 中间 key 上推 | 不再留在左右内部页中 |
| 根页分裂 | 创建新根 | 树高增加 |
root full, split:
[30]
/ \
[10,20] [40,50]
树高从 1 层增加到 2 层
这就是为什么 B+ 树增长通常是“先变宽,必要时变高”。树高增加并不频繁。
四、删除后为什么要借位或合并
删除会让页占用率下降。为了避免大量半空页浪费空间,B+ 树通常规定除根外节点至少保持一定数量的 key。当删除后低于下限时,先看相邻兄弟是否有多余 key;如果有,就重分布,也叫借位;如果没有,就把两个页合并。
最低占用 = 2
delete 30:
leaf [30,40] -> [40] 低于下限
兄弟 [10,20,25] 有富余:
借 25 后:
[10,20] | [25,40]
借位后父节点的分隔 key 也要更新,因为右侧页的最小 key 可能变了。只调整叶子页、不调整父节点,会让后续搜索走错分支。
五、合并也可能向上传播
如果兄弟页没有多余 key,就合并两个页,并从父节点删除对应分隔 key。父节点少了一个 key 后,也可能低于最低占用率,于是合并继续向上传播。极端情况下,根节点只剩一个孩子时,可以把这个孩子提升为新根,树高减 1。
merge leaves:
[10] + [20] -> [10,20]
parent 删除分隔 key 20
if parent underflow:
parent 也要借位或合并
这也是 B+ 树删除比插入更容易写错的原因:不仅要处理当前页,还要维护父节点、兄弟页和叶子链表。
六、常见误区与追问
记忆钩子:插入满了向上分裂,删除空了向旁边借,借不到再合并。
- 误区:页分裂只发生在叶子节点。 叶子分裂后父节点可能溢出,内部节点和根节点也可能继续分裂。
- 误区:叶子页分裂时中间 key 被移出叶子。 B+ 树叶子层要保留完整数据项,父节点拿到的是导航副本。
- 误区:删除后低于下限直接合并。 通常先尝试向兄弟页借位,借不到才合并。
- 追问:根节点分裂会怎样? 创建新根,树高增加 1;这也是 B+ 树唯一变高的典型路径。
- 追问:合并会不会让树变矮? 可能,根节点只剩一个孩子时可以压缩根,树高减 1。
- 追问:分裂和合并为什么影响写性能? 它们会修改多个页和父子指针,还可能引发日志、锁和刷盘成本。
七、加强记忆
B+ 树页分裂和页合并可以按“容量约束”来记:插入让页超上限,就拆成两个页,并把导航 key 交给父节点;删除让页低于下限,就先向兄弟借,借不到再合并,并同步更新父节点。叶子分裂是 copy up,内部分裂是 push up;合并和分裂都可能向上传播。把这几条讲清楚,基本就能覆盖面试对 B+ 树维护过程的追问。