什么是索引?
索引是关系数据库中对某一列或多个列的值进行预排序的数据结构,就好比书籍的目录。
为什么使用索引?
索引的出现其实就是为了提高数据库查询的效率,就好比书的目录,通过书的目录,我们可以快速精确找到我们想要的信息
不适合索引的数据结构
索引的作用是为了提高查询的效率,而可以提高查询的数据结构有很多种,我们都知道数据库选择了B+树这一数据模型作为索引的数据结构,那么为什么选择B+树,而不是其他数据结构?
数组
如果单从查询效率来看,数组显然是最合适的,我们都知道数组的查询速度快,但是当我们的数据库中存在数据更新时,假如在中间插入一个数据,那么后面的全部数据都要移动,显然数组结构比较适合静态数据不变的存储
二叉树
由二分查找思想,二叉树也是经典的提高查询速度的数据结构,但是为什么不选择二叉树是因为,索引的存储位置原因,这里首先问一下,索引存储在哪里?
索引存储在磁盘当中,首先要知道索引要加载到内存中才能查询,而从磁盘到内存的加载过程就会有磁盘I/O过程,数据查询的主要时间就是磁盘I/O的时间,我们简单理解就是I/O过程非常消耗时间,我们需要做的就是尽量减少I/O的过程,这里我们举个例子。假设一个7个节点的二叉树它可以是三层高,也同样可以是7层高,二叉树高度不好控不行,那么平衡二叉树那,我们认为的添加限定条件,限制树的高度,但是假如N比较大二叉树依然很高,所以我们换一种思路M叉树,也就是B树和B+树
B-树
B-树是一种平衡的多路查找树。B树结构如下图所示
B树每个结点最多有M棵子树,M就是B树的阶,一棵M阶的B树满足下面的特性:
- 树中每个节点最多有M棵子树
- 若根节点不是叶子结点,则最少有两棵子树
- 除根节点外的所有非终端结点,也就是中间结点,子树在[ceil(M/2),M]之间,就是有最大最小值约束
- 所有非终端结点(叶子结点)也就是中间结点,包含三种信息(关键字,子树,指针),假设一个中间结点,包含K个关键字,则子树数目为k+1,也就是说关键字和子树数目存在加一的关系
- 假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i]<Key[i+1]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k个指针,即为:P[1], P[2], …, P[k],其中 P[1] 指向关键字小于 Key[1] 的子树,P[i] 指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k] 指向关键字大于 Key[k-1] 的子树
- 所有叶子结点出现在同一个层次
B+树
B+树是基于B-树做的改进,两者差异在于:
- 有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数 +1。
- 非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最小)。
- 非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而B树中,非叶子节点既保存索引,也保存数据记录。
- 所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关
键字的大小从小到大顺序链接。下图就是一棵 B+ 树,阶数为 3,根节点中的关键字 1、18、35 分别是子节点(1,8,14),(18,24,31)和(35,41,53)中的最小值。每一层父节点的关键字都会出现在下一层的子节点的关键字中,因此在叶子节点中包括了所有的关键字信息,并且每一个叶子节点都有一个指向下一个节点的指针,这样就形成了一个链表。
在B树和B+树两者之间,我们为什么选择B+树了那,因为局部性原理和磁盘预读的特性,这两个概念读者可以自行百度一下,是计算机系统的特性,计算机读取数据是以页为单位的,大小为4K,所以说我们每次读取磁盘块大小固定的,如果我们在上层不仅存储关键字还存储数据,那么就导致我们虽然可以有多个关键字指代多个子树,但是我们空间不够,导致我们依然存不了几个子树数据。
总结一下就是,首先B+ 树查询效率更稳定。因为 B+ 树每次只有访问到叶子节点才能找到对应的数据,而在 B 树中,非叶子节点也会存储数据,这样就会造成查询效率不稳定的情况,有时候访问到了非叶子节点就可以找到关键字,而有时需要访问到叶子节点才能找到关键字。
其次,B+ 树的查询效率更高,这是因为通常 B+ 树比 B 树更矮胖(阶数更大,深度更低),查询所需要的磁盘 I/O 也会更少。同样的磁盘页大小,B+ 树可以存储更多的节点关键字。
不仅是对单个关键字的查询上,在查询范围上,B+ 树的效率也比 B 树高。这是因为所有关键字都出现在 B+ 树的叶子节点中,并通过有序链表进行了链接。而在 B 树中则需要通过中序遍历才能完成查询范围的查找,效率要低很多。