[C++项目] 储存引擎

什么是跳表

跳表全称为跳跃列表,它允许快速查询,插入和删除一个有序连续元素的数据链表。跳跃列表的平均查找和插入、删除时间复杂度都是O(logn)

快速查询是通过维护一个多层次的链表,且每一层链表中的元素是前一层链表元素的子集(见示意图)。

一开始时,算法在最稀疏的层次进行搜索,直至需要查找的元素在该层两个相邻的元素中间。这时,算法将跳转到下一个层次,重复刚才的搜索,直到找到需要查找的元素为止。

每个带有箭头的框表示一个指针, 而每行是一个稀疏子序列的链表;底部的编号框(黄色)表示有序的数据序列。

查找从顶部最稀疏的子序列向下进行, 直至需要查找的元素在该层两个相邻的元素中间。

跳表的演化过程

对于单链表来说,即使数据是已经排好序的,想要查询其中的一个数据,只能从头开始遍历链表,这样效率很低,时间复杂度很高,是 O(n)

我们可以为链表建立一个“索引”,这样查找起来就会更快,如下图所示,我们在原始链表的基础上,每两个结点提取一个结点建立索引,我们把抽取出来的结点叫做索引层或者索引,索引中存放指向原始链表结点的指针。而通过对链表加多级索引的结构,就是跳表了

跳表是用空间来换时间。跳表的效率比链表高了,但是跳表需要额外存储多级索引,所以需要的更多的内存空间。

插入节点的层数应当随机

如果我们不停的向跳表中插入元素,就可能会造成两个索引点之间的结点过多的情况。

结点过多的话,我们建立索引的优势也就没有了。所以我们需要维护索引与原始链表的大小平衡,也就是结点增多了,索引也相应增加,避免出现两个索引之间结点过多的情况,查找效率降低。

跳表是通过一个随机函数来维护这个平衡的,当我们向跳表中插入数据的的时候,我们可以选择同时把这个数据插入到索引里。

那我们插入到哪一级的索引呢,这就需要随机函数,来决定我们插入到哪一级的索引中。
即第一层的概率为0.5 第二层为0.25 第三层为0.125 以此类推 用奇偶性来完成

TPS
Transactions Per Second,意思是每秒事务数。

QPS
Queries Per Second,意思是每秒查询率。

跳表是链表结构,一条数据一个结点,如果最底层要存放2kw数据,且每次查询都要能达到二分查找的效果,2kw大概在2的24次方左右,所以,跳表大概高度在24层左右。最坏情况下,这24层数据会分散在不同的数据页里,也即是查一次数据会经历24次磁盘IO。

因此存放同样量级的数据,B+树的高度比跳表的要少,如果放在mysql数据库上来说,就是磁盘IO次数更少,因此B+树查询更快。

而针对写操作,B+树需要拆分合并索引数据页,跳表则独立插入,并根据随机函数确定层数,没有旋转和维持平衡的开销,因此跳表的写入性能会比B+树要好。

redis支持多种数据结构,里面有个有序集合,也叫ZSET。内部实现就是跳表。

redis为什么使用跳表而不使用B+树或二叉树呢?

redis 是纯纯的内存数据库

进行读写数据都是操作内存,跟磁盘没啥关系,因此也不存在磁盘IO了,所以层高就不再是跳表的劣势了。

并且前面也提到B+树是有一系列合并拆分操作的,换成红黑树或者其他AVL树的话也是各种旋转,目的也是为了保持树的平衡。

而跳表插入数据时,只需要随机一下,就知道自己要不要往上加索引,根本不用考虑前后结点的感受,也就少了旋转平衡的开销