Skip to content

08 - B+Trees: The Best Data Structure in the World (CMU Intro to Database Systems)

B+Tree

B+树是一个自平衡有序 m-way 树(m 叉树,(m/2-1 <= #keys <= m-1)),可以在 log n 的时间内完成所有的数据结构操作。
B+树的叶子 Node 是 B+树的本质,它是一个有序链表,上层的 Node 只是为了更快的跳到这个有序链表的特定位置。

B+Tree Leaf Nodes

每一个叶子 Node 都有一些 Metadata ,它们会存储 Level, Slot, Prev, Next, High Key 等数据。

通常实际的数据存储的格式是将 key 排序放在一个数组中, value 放在一个数组中。通常,插入一个新的 key 时,都会进行一次快速排序,但这不是必须的。

B+Tree Operator

Insert:

当需要 insert 时,如果叶子 Node 足够大,直接插入就好;如果不够,需要向上拆分数据结构,最坏的情况可能需要重构整棵树。
拆分就是将中间的 key 作为中点,一分为二,然后将中间的 key 存在上一层的 Node 中,最后向上递归拆分。

Delete:

delete 和 insert 基本相反。删除掉一个数据后,判断这个 Node 中的空位是否大于一半,如果不大于,那就结束了,如果大于,就会找到隔壁 Node ,看看它们的空位是否大于一半,如果大于就拿一个数据过来,更新上层 Node ,否则就合并。
合并就是将两个 Node 的数据合并成一个 Node ,然后删除上一层的 Node 中的数据,最后向上递归合并。

SELECTION CONDITIONS

联合索引遵从最左前缀的原因是,索引建立后,是按照从左到右的顺序排序的,使用联合索引时,如果从右开始,那只能全部遍历一次。

Duplicate Keys

对于重复键的解决方法通常有两个, Append Record IDOverflow Leaf Nodes

Append Record ID

通过在复合键中添加一个隐藏的记录的唯一标识符(Record ID),确保即使原始键重复,整个复合键也是唯一的。

实现简单,逻辑清晰

Overflow Leaf Nodes

允许叶 Node 在满时溢出到额外的溢出 Node ,这些溢出 Node 专门存储重复键值。

实现复杂,而且会增加树的高度,同时需要修改原本的算法,插入操作时,需要将所有涉及到溢出的溢出 Node 读取到内存中。

Merge Threshold

在传统 B+树中,合并阈值是指 Node 在删除操作后触发 Node 合并的填充率阈值 50% 。当 Node 填充率低于此阈值时,通常会触发合并操作。

在现代数据库中,例如 PostgreSQL ,把合并阈值短暂容忍为 69% ,只有当一段时间过后,或者填充率超过了 69% ,才会进行合并。

Variable-length Keys

面对可变长度键,解决方法有 Pointers, Variable-length Node, Padding, Key Map / Indirection

Pointers:

存键的地址

Variable-length Node:

使用可变长度的 Node ,实现起来困难

Padding:

对于不满足长度的 key ,在末尾填充 0 直至满足长度

Key Map / Indirection:

和 Tuple 一样,数据从末尾向开头存入,大多数数据使用它

如果键特别大, Node 放不下,那就只能溢出了。

Linear

将 Node 读取到内存中,直接扫描它。

Binary

如果数据已经排序了,二分法。

Write-Optimized B+Tree

在 MySQL 的 B+树中,非上层 Node 还会附有一个 mode log ,它类似于 Log-Structured ,每当有插入或写入进入时,直接写入 log ,当 log 满了,就放到下一层 node 的 mode log 中,直到碰到叶子 Node ,才会真正写入。

优点:减少了树的拆分和合并
缺点:实现困难,当扫描时,需要注意 mode log 中的数据