mysql的B+树

一、B+树的结点组成

B+树包含两种结点:

0、根结点:(一般区分为两种,这里我将根结点分开说明,因为根节点非常特殊而且唯一)

      若树只有一层:仅有根结点,此根结点也是叶结点,根结点中索引值个数无最少限制。

      若树为两层以及以上:最上面那一层为根结点,根结点属于内部结点,根节点中指针最少为2个。

1、叶结点:叶节点在B+树的最底层(所有叶节点都在一层),叶结点中存放索引值、指向记录的指针、指向下一个叶结点的指针。叶结点内的索引值是记录中键的拷贝,这些索引值以排好序的形式,从左到右分布在叶节点中。

2、内部结点:所有非叶结点都属于内部结点,所有内部结点上的索引值都是叶结点的副本。内部结点中存放索引值、指向某一个结点(可以是叶结点也可以是内部节点)的指针。

这两种节点的构成直观图如下:    (可以看到两者的存储空间是一样的)

典型的3种存储引擎

1、hash:

代表:nosql的redis/memcached

本质为: 基于(内存中)的hash;

所以支持 随机 的增删查改,读写的时间复杂度O(1);

但是无法支持顺序读写(注,这里指典型的hash,不是指如redis的基于跳表的zset的其他功能);

基本效果:在不需要有序遍历时,最优

2、磁盘查找树:

代表:mysql

本质为:基于(磁盘的)顺序查找树,B树/B+树;

基本效果:支持有序遍历;但数据量很大后,随机读写效率低(原因往下看);

3、lsmtree:

代表:hbase/leveldb/rocksdb

本质为: 实际落地存储的数据按key划分,形成有序的不同的文件;

结合其“先内存更新后合并落盘”的机制,尽量达到磁盘的写是顺序写,尽可能减少随机写;

对于读,需合并磁盘已有历史数据和当前未落盘的驻于内存的更新,较慢;

基本效果:也可以支持有序增删查改;写速度大幅提高;读速度稍慢;

B树

B树是一种平衡多路搜索树,B树与红黑树最大的不同在于,B树的结点可以有多个子女,从几个到几千个。那为什么又说B树与红黑树很相似呢?因为与红黑树一样,一棵含n个结点的B树的高度也为O(lgn),但可能比一棵红黑树的高度小许多,应为它的分支因子比较大。所以,B树可以在O(logn)时间内,实现各种如插入(insert),删除(delete)等动态集合操作。

B树的定义如下:

  • 根节点至少有两个子节点
  • 每个节点有M-1个key,并且以升序排列
  • 位于M-1和M key的子节点的值位于M-1 和M key对应的Value之间
  • 其它节点至少有M/2个子节点
  • 所有叶子结点位于同一层;

下图是一个M=4的4阶的B树:

B树的搜索:从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;

B树的特性:

  1. 关键字集合分布在整颗树中;
  2. 任何一个关键字出现且只出现在一个结点中;
  3. 搜索有可能在非叶子结点结束(树中所有结点都存储数据,与B+树这一点不同);
  4. 其搜索性能等价于在关键字全集内做一次二分查找;

B+树

B+树是对B树的一种变形,与B树的差异在于:

  1. 有n棵子树的结点中含有n个关键字,每个关键字不保存数据,只用来索引,所有数据都保存在叶子节点。
  2. 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
  3. 所有的非终端结点可以看成是索引部分,结点中仅含其子树(根结点)中的最大(或最小)关键字。
  4. 为所有叶子结点增加一个链指针,便于区间查找和遍历。
  5. 所有关键字都在叶子结点出现;

如下图一个M=3 的B+树:

B+树的搜索:与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;

B+的特性:

  1. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
  2. B+树的叶子结点都是相链的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

B树和B+树总结:

B树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;

B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;

为什么说B+tree比B树更适合实际应用中操作系统的文件索引和数据库索引?

(1) B+tree的磁盘读写代价更低 

B+tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+ 树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。

(2)B+tree的查询效率更加稳定 

由于非叶子结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

(3)B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)。

LSM树

大量的随机写,导致B族树在数据很大时,出现大量磁盘IO,导致速度越来越慢,lsmtree是怎么解决这个问题的:

1、尽可能减少写磁盘次数;

2、即便写磁盘,也是尽可能顺序写;

方法:

1、对数据,按key划分为若干level;

每一个level对应若干文件,包括存在于内存中和落盘了的;

文件内key都是有序的,同级的各个文件之间,一般也有序

如leveldb/rocksdb,level0对应于内存中的数据(0.sst),后面的依次是1、2、3、...的各级文件(默认到level6级)

2、写时,先写对应于内存的最低level的文件;这是之所以写的快的一个重要原因

存在于内存的数据,也是被持久化的以防丢失;

存在于内存的数据,到达一定大小后,被合并到下一级文件落盘;

3、落盘后的各级文件,也会定期进行排序加合并(compact),合并后数据进入下一层level;

这样的写入操作,大多数的写,都是对一个磁盘页顺序的写,或者申请新磁盘页写,而不再是随机写

总结lsmtree的写为什么快的两大原因:

1、每次写,都是在写内存;

2、定期合并写入磁盘,产生的写都是按key顺序写,而不是随机查找key再写;

可见compact是个很重要的事情了,下面是基于lsmtree引擎的rocksdb的compact过程:

首先看一下rocksdb的各级文件组织形式:

然后,各级的每个文件,内部都是按key有序,除了内存对应的level0文件,各级的内部文件之间,也是按key有序的;

这样,查找一个key,很方便二分查找(当然还有bloomfilter等的进一步优化)

再然后,每一级的数据到达一定阈值时,会触发排序归并,简单说,就是在两个level的文件中,把key有重叠的部分,合并到高层level的文件里

这个在lsmtree里,叫数据压缩(compact);

对于rocksdb,除了内存那个level0到level1的compact,其他各级之间的compact是可以并行的;通常设置较小的level0到level1的compact阈值,加快这一层的compact

良好的归并策略的配置,使数据尽可能集中在最高层(90%以上),而不是中间层,这样有利于compact的速度;

另外,对于基于lsmtree的(rocksdb的)读,需要在各级文件中二分查找,磁盘IO也不少,此外还需要关注level0里的对于这个key的操作,比较明显的优化是,通过bloomfilter略掉肯定不存在该key的文件,减少无谓查找;

猜你喜欢

转载自blog.csdn.net/qq_33243164/article/details/89509595