常见的查找数据结构包括HASH表和二叉树(红黑树)。
Hash表是普通数组的一个扩展,它支持O(1)的操作,包括查询,插入,以及删除。但是Hash支持O (1)时,是一个比较理想的状态,要求很好的Hash函数以及比较多的冗余内存。这部分咱们暂时不展开。
二叉树是另一种比较经典的查找数据结构,其特点如下:
一个典型的二叉树如图所示:
虽然Hash表或者二叉树是比较常见的查找数据结构,但是大部分数据库存储引擎并不使用它们作为主要的数据结构(也有例外的),而是使用B-Tree以及B-Tree的变种B+Tree。
提问:有哪些数据库存储引擎是使用Hash表或者二叉树作为主要的数据结构,为什么它们使用这些数据结构?
B-Tree和二叉树类似,有如下特性:
下图为一个N=4的B-Tree:
大多数数据库存储引擎使用B-Tree的原因有两个:
在数据库存储引擎中,最常使用的是B-Tree的变种:B+Tree。B+Tree除了有B-Tree所有的特性,还有另外几个特性:
下图为一个N为4的B+Tree:
B+Tree一般至少提供三种操作:查询,插入以及删B+Tree查询过程:
B+Tree的插入相对查询要复杂一些,插入要涉及两个新操作:分裂和增加新根节点。B+Tree的节点能够容纳的值是N,如果一旦超过N,一个节点就要分裂成两个节点;如果根节点分裂了,就需要创建一个新根节点。
B+Tree的插入操作:
B+Tree删除是所有操作中最复杂的。除了需要考虑到节点合并以及根节点删除的操作,还需要考虑到删除操作带来的连续合并操作和邻近节点寻找的过程。
B+Tree删除操作:
B+Tree节点一般值的个数不少于N/2,所以如果碰到某个节点的值个数少于N/2,需要合并邻近节点。但是,邻近节点值的个数大于N/2,就可能出现合并后节点的值个数大于N。
寻找邻近节点是节点合并的前置操作,需要先找到邻近节点,才能让邻近节点和当前节点进行合并操作。但是有时候邻近节点和当前节点有时候并不拥有同一个父节点,这个时候会极大影响后面的节点合并操作,比如:节点发生合并后,如何从父节点中删除一个值?节点内容调整,如何调整父节点中的值。
下面是邻近节点和当前节点不共享父节点的情况:
父节点中删除一个值,需要调整两个节点中的父节点内容,涉及的节点就比较多。由于这个例子中,两个节点的父节点共享父节点,涉及的节点较少;如果是父父父节点才相同,涉及的节点就会更多,更难处理。为了处理这个问题,我们先定义一个最佳邻近节点:拥有相同父节点中,值个数最小的邻近节点为最佳邻近节点。
选择最佳邻近节点合并操作:
如果根节点恰好只有两个子节点,而这两个子节点发生了合并,这个需要删除旧的根节点,合并后的节点为新的根节点。
删除不但会引起叶子节点的合并,也会引起其他节点合并。叶子节点合并了,会从其父节点中删除一个值,这样就可能会引发父节点的合并,而父节点删除又有可能导致父父节点的删除,从而引发父父节点的合并……,以此类推,一直删除到跟节点。
合并非叶子节点可以认为是分裂非叶子节点的“逆操作”,分裂时,需要从分裂后的第一个节点中取出一个值插入父节点;合并时,也需要从父节点从取出一个值放入合并的节点中。
B+Tree的所有操作,过程都需要遵循B+Tree的基本特性,如:节点内的值按照大小顺序排列等。B+Tree所有操作都是O(logN)的时间复杂度,所以B+Tree操作很稳定,正因为如此B+Tree在传统关系型数据库中广泛使用。
提问:新兴非传统数据库会使用哪些数据结构?与B+Tree比起有什么优缺点?除了B+Tree还有哪些B-Tree的变种,它们有什么优缺点?
本文介绍了B+Tree的基本构造以及操作过程,但是按照这些描述比较难以实现一个内存版的B+Tree,这就是算法和实践的差异,实践中还需要考虑很多工程问题。正因为为了解决这些工程问题,真正实现的B+Tree需要根据不同的情况进行调整。