前面介绍完了 Hash Table,在数据库系统中,它可以用于一些 sql 执行时的临时数据结构,或者用来存储一些元数据信息,也可以作为表的 Hash 索引,但是对于表索引,在更通用的场景下, B+ 树是更广泛的选择。
创新互联专注于企业成都全网营销推广、网站重做改版、黄山区网站定制设计、自适应品牌网站建设、成都h5网站建设、商城网站定制开发、集团公司官网建设、成都外贸网站制作、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为黄山区等各大城市提供网站开发制作服务。
表索引(Index)指的是表中数据的一些子集,这些子集能够标识表中数据的一些特征,以便能够快速的去查找 table 中的数据,并且数据库能够保证,表中数据和表索引数据的一致性。
B+ Tree 实际上是一种平衡树结构,它能够保证数据有序存储,可以在平均 O(log n) 复杂度下查询、插入、删除数据。
它实际上类似二叉平衡树,但是每个节点可以拥有不止 2 个子节点,并且能够适配数据库需要读取整块数据的需求,提升数据读写的效率。
B+ 树是一个多叉平衡树,例如 M 个分叉指的是每个 inner node 都有 M 个路径到子节点,具有以下基本特征:
下图所示,是一个 B+ Tree 的例子。
最底层的节点叫做 leaf node 叶子节点,上层的叫做 inner node,最顶层的 inner node 就是根节点(root node)。
inner node 中不会存储实际的数据,而是存储 key 以及指向其子节点的指针。
叶子节点之间有指针串联,方便进行 scan 操作;每个叶子节点中存储了实际的 key/value 数据。
叶子节点的内部结构,一般有两种不同的布局方式,一种是很常见的,存储一个 page id,并且将索引的 key 和 value 都存放到节点中,然后一个 page 指向下一个 page。
这样是将 key/value 保存在了一起,并不利于顺序扫描,如果我们只需要扫描 key 的话,那么会有一些额外的消耗。
另一种方式是将 key 和 value 分开,结构大致如下所示:
其中包含了一些元数据信息,例如当前的层数,空闲空间等,以及一个有序的 key 列表,并且每一个 key 指向了它自己的 value。
而实际 value 所表示的数据,各个系统有不同的实现,大致有两种:一是存储一个记录 id,指向磁盘的 page 中数据的实际位置;二是直接就存储数据本身。
B+ 树中插入操作大致流程如下:
重新分布该叶子节点上的数据,以最中间的 key 为界,右边的数据分裂到新的节点中
分裂的时候,需要将叶子节点中间的 key 推送到上层父节点
如果上层父节点又需要分裂,则重复执行上述过程
delete 操作大致是和 insert 相反的,插入的时候,如果一个节点上的数据满了,则需要分裂;而删除时,如果一个节点中的数据少于了 M/2-1,则破坏了 B+ Tree 的定义,这时候需要将节点进行合并,称为 merge。
大致过程如下:
尝试重分布,如果邻近节点有富余,则从邻近的节点中拿一个 key
如果邻近节点也没有多余的数据,则尝试和兄弟节点合并
这里有一个网站,动态展示了 B+ 树的插入、删除、查找过程,能够更好帮你理解 B+ 树。https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
接下来在简要分析下关于 B+ Tree 的一些设计问题。
Node Size
对于 B+ 树中每个节点的大小,这其实并不确定,或者说依赖于硬件环境和数据库类型,sql 查询类型等因素。
大致来说,存储介质速度越慢,则一个节点的容量就越大,道理也很简单。当在慢速的介质中,例如磁盘,我么肯定希望能够一次性多读取一部分数据到内存中,尽量避免多次随机 IO。在越快速的设备上,则节点的容量越小。
可以参考内存中的 cache line 和操作系统维护的 page size。MySQL 的 B+ 树节点大小一般是 16KB。
Merge Threshold
前面说到了删除数据时可能会触发 B+ 树的 merge 节点操作,但是在实际的实现中,一些数据库系统并不是只要满足条件就会马上执行,而是累积到一段时间之后,再进行 merge。
因为 merge 是一个很“昂贵”的操作,涉及到磁盘上的数据调整分布,一些系统中会有一些后台进程,定期去触发这个操作。
Variable-Length Keys
前面提到的 B+ Tree 中存储的都是固定 size 的 key,但是实际环境中,我们的 key 或者 value 都有可能是不定长的。针对不定长的 key,一般有这几种解决办法:
1.节点中不存储实际的 key,而是存储指向实际 tuple 属性的指针,这样的话虽然能够固定大小,但是指针并没有 key 本身具有的特征,范围扫描低效,实际使用并不多。
2.节点的 size 也不固定,来适配不同长度的 key
3.对齐,总是将 key 对齐为其类型的大小,而不管 key 的 size 有多大。这种方式缺点也显而易见,就是可能会浪费一定的空间。
4.使用类似 slot page 的组织方式,将 key 存在一个有序的集合中,并且指向其实际的数据。
数据库中的索引,有时候可以存在重复的数据,除了唯一索引外。因此,在 B+ Tree 存储数据的时候,需要对重复的 key 进行处理。
Duplicate Keys
存储重复 key 很好理解,就是我们可以把重复的 key 也当做是一个单独的 key 进行存储即可。
Va
lue List
value list 就是对重复 key 的 value 维护了一个链表,将其串联起来。
最后再来看下关于 B+ 树在设计时的一些优化方案。
Prefix Compression
因为 B+ 树底层叶子节点的数据是有序排列的,因此存储在同一个叶子节点的数据,有很大可能是具有相同的特征的,例如可能是类似的,拥有相同的前缀。
所以,针对那些有相同前缀的数据,可以只存储一份前缀数据,而不用每个 key 都单独存储一份。
Suffix Truncation
前面提到过 B+ 树中 inner node 只存储了 key 和索引信息,并不存储数据,只是做为一个辅助查找的索引节点。因此 inner node 中的 key 并不用是完整的 key,只要能够起到区分查找的作用就可以了。
例如上面的例子,由于这两个 key 所有字符都不一致,因此并不需要存储完整的 key,只需要存储能够帮助查找到下一级节点的信息就可以了。
Bulk Insert
最好的构建 B+ 树的方式,是将一组有序的数据从下到上构建 B+ 树的多级索引,这样不会存在 B+ 树的分裂或者合并,效率是最高的。
因此如果有一部分初始化数据需要插入到 B+ 树中,可以先将其排序,然后直接构建 B+ 树。
Pointer Swizzling
B+ 树遍历 page 的时候,每次都需要从 buffer pool 中获取 page 的位置信息,然后 B+ 树根据这个位置去获取 Buffer Pool 中的 page 数据。
例如上图中,需要获取 page id 为 2 的 page,则会先从 page table 中获取,Buffer Pool 判断这个 page 是否在内存中,如果不在则加载这个 page 到内存,并且返回一个 page 的指针(page 的实际位置)给 B+ 树。
如果 B+ 树确认扫描到的 page 肯定在内存中,那么可以直接存储 page 指针,省去了从 page table 中寻址的开销。
这个优化比较适用于 B+ 树的上层节点,因为 B+ 树上面的几层节点容量相对较小,并且会被频繁的访问到,可以缓存到内存中加速 B+ 树的查询。
这一节讲述了 B+ 树的一些基本概念,相信读者能够对其有一个基本的理解了,在大多数情况下,B+ 树是一个在数据库中应用非常广泛的结构。
网页题目:CMU15445学习之TreeIndexI
文章起源:http://www.shufengxianlan.com/qtweb/news32/31732.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联