一、MySQL B+树索引 和 Redis 中跳表索引
在 MySQL
中常用的索引是 B+
树索引,而 Redis
中,例如 zset
使用的的是跳表索引,两者有什么区别呢,MySQL
为什么不使用 跳表 呢?或者说 Redis
中为什么不使用 B+
树 呢?
下面先分别了解下 B+
树和跳表的工作原理。
二、B+树
B+
树是 B
树的变体,B+
树对比 B
树,将B
树的一个节点同时存放主键和数据的形式,改为叶子节点和非叶子节点形式。其中非叶子节点不存储具体数据,只存放主键和指向下一级数据的指针。而叶子节点在最尾端,存放主键和指向数据行的地址。叶子节点和非叶子节点采用指针连接,提高区间访问的性能。结构如下:
MySql
每次从磁盘读取数据是以页的形式读取,默认一页的大小为16k
。这里假设每个主键占用8
个字节,指针占6
个字节,那么mysql
一次从磁盘读取 16k 就可以拿到 1024*16/(8+6)= 1170
条数据,假设树的高度为两级,这两次磁盘 IO
操作就可以大概涵盖 1170*1170 =1368900
查不到一百多万条数据,由此可见 B+树
效率之高。
此外 B+
树能保证如此高的查询性能,关键点还在于B+
树结构的平衡,这样就需要在插入数据的时候额外计算和调整树的平衡操作,一定程度降低了写的性能,下面来看下写的过程。
比如,当一个树节点最多可以存放5
个索引的时候,在写入前 5
个数据的时候都不会触发树的平衡:
但是写入第6
个数据的时候,因为一个节点最大放5
个索引,所以下一步需要平衡,只能拆成二级结构:
由于最大的节点中有三个索引,所以下面两次写入,都不会触发平衡:
再次写入的时候,就需要进行平衡了,但是最上层的节点只有一个索引,所以可以在第一层加一个索引,第二层分三个节点:
依次类推,可以极大的较少树的层级。
所以 B+
树通过空间换时间的方式,使用不同的叶子结点构建索引层级,将查询的时间复杂度从全表检索的O(n)
优化为O(lg(n))
。
三、跳表
跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
例如上述结构,假如需要查询主键为 7
的数据,首先在第一层从左向右可以找出在 6
之后、11
之前,然后进到 6
的下一层,继续从左向右可以找出在 6
之后、8
之前,然后进到 6
的下一层,从左向右就可以找到 7
了。
整体有点类似二叉树,但最后一层是全部的数据,上层通过增加跳跃点的方式加速检索的过程,并且跳跃点采用随机概率的方式决定是否增加,如果是两层跳跃层的话,在第一层加索引的概率就是 50%
概率,到第二层就是 25%
的概率 ,这样如何数据量样本足够大的话,数据的分布可以基本达到二分的效果。
由于每次写入数据,都随机决定是否要在每层增加索引,所以写入效率非常高。
例如写入数据9,首先在最后一层新增数据:
四、总结
MySQL
是磁盘存储的数据库系统,其中性能瓶颈在于磁盘IO
的性能,而 B+
树,以叶子节点和非叶子节点存储多索引的方式极大降低树的层级,在磁盘访问时能够保持较好的局部性,也大大降低了磁盘的读取次数,可以有效提升读取的性能。而跳表由于其随机指针的特性,在磁盘访问时可能导致更多的随机IO
操作,影响访问效率。跳表在存储空间利用率上也不占优势。跳表需要维护多级索引,可能会导致额外的空间消耗,尤其是在数据量较大时,这种空间开销会变得非常显著。因此对比下来, B+
树更适合 MySQL
。
而 Redis
是基于内存的数据库,数据操作都在内存中进行,无需关注磁盘IO
,所以即使层级增大也影响不大,但是 B+
树写数据时需要进行树平衡操作,反而影响了写的性能。而且跳表相对于B+
树来说实现更加简单,代码量更少,容易理解和维护。在内存数据库中,简单且高效的实现是非常重要的考虑因素。跳表在支持范围查询方面比较灵活,插入、删除和查找操作的时间复杂度都是O(log n)
,这使得跳表在处理范围查询时表现较好。因此对比下来 Reids
中跳表的效果更好。