1. 背景
B+ 树在数据库中是最常见的索引结构,在MYSQL 中聚簇索引和非聚簇 辅助索引 用的都是 B+ 树的结构。
在聊 B+ 树之前先聊一下B+ 树是怎么来的,解决了什么问题,才能更好的了解为什么我们需要 B+ 树。
2. 二分查找
二分也称为折半查找,如果我们获得了一组有序的数据集合,比如 1,3,4,5,7,10. 如果想在集合中找到数字为7的值,如果是顺序查找的话平均查找次数是 (1+2+3+4+5+6) / 6 = 3.5,如果使用折半查找的话,则是 (3+2+3+1+2+3) /6 = 2.3. 如果算时间复杂度的话,顺序查找的时间复杂度是 O(n),折半查找的时间复杂度是 O(logn) .
3. 二叉树
以上面的例子为例(1,3,4,5,7,10),二叉树是如下的结构,符合我们前面说的时间复杂度和查找次数。
在二叉树的定义中,每个节点的左侧节点小于当前节点的数据,右侧节点大于当前节点的数据,每个节点最多有2个叶子节点。
二叉树的问题,二叉树因为没有规定树深度的最小差额,所以可能会得到如下的二叉树,这个二叉树的平均查询查找次数为 (1+2+3+4+5+5) /6 = 3.3 , 这已经和顺序查找 3.5 很接近了。如果二叉树是只有一边的话,那就和顺序表的查询效率一样了,所以树的查询效率很大一部分就是在减少树的高度。
mysql 使用 B+树 也是因为减少树的高度,每减少一次树的高度在 mysql 的视角就减少了一次 IO。
4. 平衡二叉树
因为二叉树有上面提到的问题,所以才引入了平衡二叉树,也称为 AVL 树(Adelson-Velsky and Landis)。
平衡二叉树定义了,每个节点的深度不能相差大于1,如果大于 1的话,AVL 树会自己通过左旋和右旋进行自平衡。
4.1. 左旋 右旋
左旋和右旋的思路其实是一样的,围绕着 7 或者 4 这个点,进行左旋和右旋之后,可以互转。
从理解上只需要理解转变2个指针即可。
右旋:
- 自己变为左子树的右子树(7变成4的右子树)
- 左子树的右子树变成自己的左子树(5变成7的左子树)
左旋:
- 自己变为右子树的左子树(4变成7的左子树)
- 右子树的左子树变成自己的右子树(5变成4的右子树)
java 代码如下:
/** * (x) (y) * / \ / * (x1) (y) --> (x) * / / \ * (T2) (x1) (T2) * * 左旋操作 */ private Node leftRotate(Node x) { Node y = x.right; Node T2 = y.left; // Perform rotation y.left = x; // x 的右子树 x.right = T2; // Return new root return y; } /** * (y) (x) * / \ / \ * (x) (y1) --> (x1) (y) * / \ / \ * (x1) (T2) (T2) (y1) * 右旋 * @param y * @return */ private Node rightRotate(Node y) { Node x = y.left; Node T2 = x.right; // Perform rotation x.right = y; y.left = T2; // Return new root return x; }
通过程序来构建上面图例中的树,进行左旋后再进行候选,会获得如下结果。
before reBalance 4 / \ / \ 3 7 / \ 5 10 after leftRotate 7 / \ / \ 4 10 / \ 3 5 after rightRotate 4 / \ / \ 3 7 / \ 5 10
5. B树
B树,B树的B是 balance 的意思,所以B树是平衡树,和平衡二叉树的最大的区别就是B树是一颗多叉树。
为什么需要 B树,其原因是还是为了性能考虑,上文有提到树的查询效率取决于树的深度,深度越深的树,查询效率越慢,深度越是浅,查询则效率越是高,那减少深度的一大方法就是把二叉树变成多叉树。
如果数据库的索引以 B+ 树来构造,我们会得到如下的一棵树。
每个节点包含如下几个数据:
- 索引关键值,用于寻址
- 子节点的磁盘指针,用于定位下个子节点的磁盘位置,如图中 P1 P2
- 数据,索引对应的真实数据
B树的特点:
- 因为是平衡多叉树,所以每个节点保存的数据相对于平衡二叉树来说增多了
- 每个节点除了保存索引值,还保存实际的数据
6.B+ 树
B+树可以说是 B树的升级版,在B树的基础上更进一步的提高了树的稳定性、提升查询性能。
以上面的图可以知道,如果想提高性能,需要减少的树的高度,因为树的每多一个深度就意味着查询多一次 IO,B+ 树优化的核心思路:
- 每一个磁盘块上,尽量保存多的节点,通过这个来减少树的深度。
- 叶子节点和叶子节点之间,通过链表串联起来,以提升范围查询的效率。
B+ 树和 B树的区别:
-
所有的索引值都在叶子节点,非叶子节点只保存关键的数据,以增加磁盘块存储关键信息的量,从而减少树的高度,减少查询时候 IO 的次数。
-
所有的索引值都在叶子节点,而B树因为在非叶子节点会保存数据,部分索引子如果在非叶子节点存在的话,在叶子节点不会出现,比如上面 B树的 10, 20, 50 等节点,B+树把所有索引都保存在了叶子节点,有2个好处
-
查询效率稳定,几乎等于 O(logn)
-
容易范围查找,在范围查找的时候,查找自己的兄弟节点,不需要往上追溯查询父节点。
-
叶子节点通过链表关联,方便范围查找。