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 ID 和 Overflow 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 放不下,那就只能溢出了。
Intra-Node Search
Linear
将 Node 读取到内存中,直接扫描它。
Binary
如果数据已经排序了,二分法。
Write-Optimized B+Tree
在 MySQL 的 B+树中,非上层 Node 还会附有一个 mode log ,它类似于 Log-Structured ,每当有插入或写入进入时,直接写入 log ,当 log 满了,就放到下一层 node 的 mode log 中,直到碰到叶子 Node ,才会真正写入。
优点:减少了树的拆分和合并
缺点:实现困难,当扫描时,需要注意 mode log 中的数据