一、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 中跳表的效果更好。