b,b+ tree, LSM tree

B树和B+树总结:

在这里插入图片描述

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

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

  3. B+树虽然优点很多,但是B树也有优点,其优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。下面是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树不支持这样的操作(或者说效率太低)。

  • 插入操作
    在这里插入图片描述
    在这里插入图片描述
    插入95后,经历两次分裂和上溢。如果是从叶节点分裂,分裂之后两个节点关键值数量不变,总指针数加一;如果从内部节点分裂,右节点不保存中间值,关键值数量减一,总指针数不变。
    在这里插入图片描述

可以看到,不管怎么变化,B+树总是会保持平衡。但是为了保持平衡,对于新插入的键值可能需要做大量的拆分页(split)操作,而B+树主要用于磁盘,因此页的拆分意味着磁盘的操作,应该在可能的情况下尽量减少页的拆分。因此,B+树提供了旋转(rotation)的功能。旋转发生在leaf page已经满了但是其左右兄弟还没满的情况,首先考虑左兄弟。

在这里插入图片描述
插入70后,旋转。假如是左邻居节点有空间,对于叶子节点来说,需只要把第0个键值移到左邻居节点。对于内部节点来说,需要把1个键值(第0个为哨兵键值)移到父节点,再把父节点中原来的键值移到左邻居节点。
在这里插入图片描述

  • 删除操作
    和插入时首先考虑旋转操作一样,在节点填充不满足填充因子时,首先考虑从兄弟节点借一个节关键字。
    在这里插入图片描述
    第一种情况中,如果删除的节点的键值同时也是index page中某个节点的键值,如果不替换index page中的节点也不会改变b+树的性质。

LSM tree

跳表

在这里插入图片描述

  • 查询时从上往下

  • 往跳表中增加Entry44的流程:
    找到底层列表(S0)中39的位置,在其后插入Entry44。假设随机函数取值为1,接着回到S1中38的位置,在其后插入Entry44,并和底层列表S0中的Entry44连接起来形成Entry44的Tower。假设随机函数取值为0,则插入过程终止。

  • 无锁skiplist

        void Insert(Node *node) {
    	node->level = RandLevel(2, MAX_LEVEL);
    	InsertInternal(head, node->level, node);
    }
    Node *InsertInternal(Node *prev, int lv, Node *node) {
    	Node *next = prev->next[lv];
    	while (next != NULL && next->item < node->item) {
    		prev = next;
    		next = prev->next[lv];
    	}
    	if (next == NULL || next->item > node->item) {
    		if (lv != 0) {
    			if (InsertInternal(prev, lv - 1, node) != NULL) {
    				ListInsert(prev, node, lv);
    			}
    		}
    	} else if (next->item == node->item) {
    		return NULL;
    	}
    	return node;
    }
    

    这里只考虑插入操作,把不同层中键值相同的节点用一个节点表示,每个节点有一个指针数组,记录在不同层中的后继节点的地址。首先用一个随机函数生成带插入节点的最大层数,从最大层数开始,利用无锁队列搜索带插入节点的前继节点并插入,然后把前继节点传递给下一层,以此类推。

LSM

LSM的主要思想是用顺序i/o操作替代随机i/o操作。
和B+树不同,LSM不会在原数据处更新,而是直接添加一条更新记录。
更新记录首先写在内存中,等到积攒到一定数量后在写入磁盘的sstable中。 因次,一个key对应的记录可能存在于多个sstable中,只有最新的记录才是有用的,所以需要不断地通过合并sstable来消除冗余数据。确定某个key是否在sstable中可以使用布隆过滤器。
在leveldb中,每一层sstable的数量有限制,并且每层的总大小逐层变大。一个key在一层只有一个记录,当一层的大小超过限制时,从中选出一个sstable,然后和下一层中有交集的sstable合并。以此类推。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值