赞
踩
有一种称为 B 树的特定数据结构,人们还使用该术语来泛指一类平衡树数据结构:
B+Tree 是一种自平衡【self-balance】、有序【ordered】的树数据结构,允许在 O(log n) 内进行搜索【search】、顺序访问【sequential access】、插入【insertion】和删除【deletion】。
B+Tree 是一种 M 路搜索树,具有以下属性:
每个 B+Tree 节点都由键/值对的数组组成,而该数组(通常)基于键有序,并将所有 NULL 键存储在第一个或最后一个叶节点中。
PS : 在上图中,我们可以看到,我们有兄弟指针,也有向下的指针,但是就是没有向上的指针。这是当我们开始在这些节点上取锁存器时,我们不想让一个线程从上往下取另一个线程从下往上取,因为那样会造成死锁。兄弟阵阵也有这个问题,但是在下一讲中会给出解决办法。
第一种叶节点中,key和value前后相依,而最左最右分别指向前后节点的 Page ID
第二种叶节点中,key和value分开存储,并保证有序,同时会维护一些其他原数据,比如层级【level】,以及插槽数【slots】,这对于恢复【Recover】也很有用。
基于上面两张图,我们重点关注叶节点中的值【value】存储的方法:
【注】Record ID 是Page ID 与偏移量(或者说是插槽 ID)的组合
原始 B 树将键和值存储在树中的所有节点中,这样做更节省空间,因为每个键仅在树中出现一次。
而B+树仅在叶节点中存储值。 内部节点仅指导搜索过程。
这样的好处是,当我们进行顺序扫描查询时,B树必须做一些向上向下的遍历,这期间也会涉及节点锁存器【latch】的操作。而对于B+树,我们只需要根据导航找打页节点,就可以顺序扫描,而无序关注父节点上的锁存器【latch】操作,这样可以带来更好的并发性,并且串行IO要比随机IO快得多。
PS 要分割内部节点,请均匀地重新分配条目,但要将中间键上推到父节点。
在哈希表中,我们唯一可以做的操作,就是哈希键【hash key】等于【=】我要找的 key.。我们没有办法做诸如小于大于的操作,甚至不可以做部分 key 查询,我们必须查询完整的key,比如当我们有一个ABC三列的索引,我们没办法查询只有AB列的key。
对于哈希索引,我们的搜索键【search key】中必须有所有属性【attributes】。
而对于B+树索引,我们必须要求搜索【search key】中必须有所有属性【attributes】,它可以只包含部分属性。
示例:<a,b,c> 上的索引
但是,并非所有 DBMS 都支持这一点,oracle 通过跳跃扫描【skip scan】实现了第三点。
栗子:
假设我们有A和B列上的 b+ 树索引,下面进行前缀查询【Prefix Search】
case 1 我们的查询键是(1,2)
case 2 我们的查询键是(1,*)
case 3 我们的查询键是(*,1)
目前有两种处理重复键的方法
Append Record ID 栗子:
1️⃣ 在如下的 B+ 树中,我们将记录 ID 【Record ID】作为键的一部分,可以看到它的组成是 key + RecordID
2️⃣ 此时,我们想要在此 B+ 树中插入元素 6,数据库系统会负责将插入语句转换为 :
3️⃣ 而注意此时叶节点已经满了,我们将元素做拆分这样就可以插入元素 6 了。需要注意的是,如果6 所在的列上有唯一索引,那么就无需这种特殊处理了。
Overflow Leaf Nodes 栗子:
1️⃣ 同样是 插入元素 6 ,我们计算的到叶节点,而该叶节点满了,我们意识到插入元素在该叶节点中有重复记录,因此我们增设一个溢出页,并将元素插入其中。
2️⃣ 我们可以继续插入重复的元素,比如插入 7 ,
3️⃣ 再次插入 6
表【table】按主键指定的排序顺序进行存储,这可以是堆组织存储【heap-organized storage】,也可以是索引组织存储【index-organized storage】。
一些 DBMS (比如MySQL)始终使用聚集索引,如果表不包含主键,DBMS 将自动创建隐藏主键。
当我们进行扫描时,假设我们按索引组织存储,当我们开始扫描叶节点,来查找所有我查找的元组时,我们可以保证按照键顺序所定义的排序顺序来获得页面【page】。
遍历到最左边的节点,然后从所有叶节点中检索元组。
而以元素在非聚簇索引中出现的顺序进行扫描,这样会导致很高的重复 IO 读取,从而降级性能。
更好的方法是找到查询所需的所有元组,然后根据其 Page ID 对它们进行排序,再顺序读取,这样每个页面只需要检出一次。
更多关于 B + 树的设计决策,请参考 Google 的书 Modern B-tree techniques | IEEE Conference Publication | IEEE Xplore
在前面介绍中,我们知道页与节点是1:1大小的,但是某些数据库,比如 DB2,允许对某个表或索引,单独设置其数据库中页的大小。依赖于你所在的硬件,你可以设置不同的页大小。比如你的硬件存储越慢,那么你就应该设置更高的页大小,以减少磁盘 IO。
某些 DBMS 在节点半满时并一定会合并节点
延迟合并操作可以减少重组【reorganization】量。
最好只让较小的节点存在,然后定期重建整个树。
这就是为什么 PostgreSQL 将他们的 B+Tree 称为“非平衡”B+Tree (nbtree)。
方法1:指针,即实际上我们并不会将键本身存在节点中,而是只需存储一个到它的指针,但是这会带来随机 IO。
方法:可变长度节点【Variable-Length Nodes】,目前为止只有一些学术数据库才使用这种方案
方法3:填充【Padding】
方法4:键映射/间接【Key Map / Indirection】,与页内的可变长处理方案一样,详见 slotted page
一旦我们进入到某个节点,我们首先将其放入内存,然后在其中查找键,以决策我们是导航到哪个子节点上。
那么节点内搜索的方案有哪些?
方法1:线性
栗子:
1️⃣ 顺序扫描
2️⃣ 与其逐个比较,我们可以利用 SIMD,并行的与 8 进行比较运算,得到最终的输出结果:
当没有匹配的键时,我们可以继续向下计算,这次就有了匹配项。SIMD 时高效的,但是它仍然时线性的,只不过是批量的。
方法 2 :如果键是有序的,我们可以使用二分查找
方法 3 :插值【Interpolation】,当你知道你的键没有间隙时,且总是单调增/减的,我么可以通过简单的数学计算出对应键的位置。
同一叶节点中的有序键可能具有相同的前缀。
与其每次都存储整个键,而不如提取公共前缀并仅存储每个键的唯一后缀。
非唯一索引最终可能会在叶节点中存储同一键的多个副本。
叶节点可以只存储键一次,然后维护具有该键的元组的“倒排列表【posting list】”(类似于我们讨论的哈希表)。
首先,我们知道内部节点中的键仅用于“引导流量”,因此我们可能不需要存储整个完整的键,而只需要存储将探测正确路由到索引所需的最小前缀即可】。
1️⃣ 之前
2️⃣ 之后
节点使用 Page ID 来引用索引树中的其他节点。
DBMS 在遍历期间必须从页表中获取内存位置。
如果页面始终固定【Pin】在缓冲池中,那么我们可以存储原始指针而不是页面 ID。 这避免了从页表中查找地址
栗子:
1️⃣ 当我们查找大于 3 的元素时,我们与 6 比较,得出应该导航到左子节点,假设其 Page ID = 2,那么我们请求 Buffer Pool 给予我 Page ID = 2的页面的指针(内存地址)。
2️⃣ 接来下我们可能需要查询其兄弟节点,即 Page ID = 3,我们又需要回到 BufferPool 请求指针。
3️⃣ 假设我们将页固定在内存中,不会被驱逐,那么我们可以直接替换树中的 Page ID 为真实内存指针,这样必须每次都要去页表中换取对应内存指针了。
为现有表构建新的 B+Tree 的最快方法是首先对键进行排序,然后从下往上构建索引。 这个在 MySQL 官方文档中叫 Sorted Index Builds。
在最后一个阶段中,索引条目可以是一条条插入的,但是这样速度也太慢了。此方法涉及打开 B 树游标以查找插入位置,然后使用乐观插入将条目插入到 B 树页面中。 如果由于页面已满而导致插入失败,则将执行悲观插入,这涉及打开 B 树游标并根据需要拆分【spli】和合并【merge】 B 树节点以为条目找到空间。 这种“自上而下”建立索引的方法的缺点是搜索插入位置的成本以及B树节点的不断分裂和合并。
排序索引构建【Sorted Index Build】使用“自下而上”的方法来构建索引。
当 DBMS 必须拆分/合并节点时,修改 B+ 树的成本很高。
如果有一种方法可以延迟更新,然后批量应用多个更改,该怎么办?
解决方法:不是立即应用更新,而是将对键/值条目的更改存储在内部节点的日志缓冲区中。简而言之,每一个 root 节点和 inner 节点,会派生出一个 mod log,我们的更新不会立即传播到叶节点,我们会违反 b+ 树的约束,即叶节点是真正的值所在的地方,而是可以将条目【entry】插入到 mod log 中。
当缓冲区已满时,更新会逐步级联到较低的节点。
栗子:
1️⃣ 我们现在想插入元素7,我们没有详细查找节点,而是直接将其写入到跟节点的 mod log 中
2️⃣ 然后我们想删除10
3️⃣ 现在我们想查找元素10,我们会先搜索下 mod log ,在其中我们发现元素 10 被删除了,那么我们页不需要向下查找了
4️⃣ 现在我们插入元素 40,根节点的 mod log 满了,我们会将日志传播到子节点中
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。