GOOD NIGHT
前言
上一篇中,我们已经了解到了索引的基本概念和一些用法。那索引为什么会提升查询的速度,以及索引究竟是怎么工作的呢?也许大家心里还是有一些迷茫,这一切,还要从索引背后的算法说起。
GOOD NIGHT
概述
大家知道数据库的索引和数据,一般都是存储在硬盘中的,这样利于数据的持久化和永久保存。因此在我们查找数据的时候,就会产生硬盘的I/O操作。而硬盘相较于内存而言,速度是比较慢的,所以如何减少硬盘的I/O操作,就是索引背后的算法存在的意义。
GOOD NIGHT
二分法
二分法是一种常用的查找算法,相较于逐个遍历查找而言,二分法的时间复杂度为O(log2n)。为了帮助大家理解和回忆二分查找,我画了一张二分查找的流程图:
从上述流程来看,我们要事先对要查找的数组或集合进行排序,而且每次会寻找中间节点进行范围的划分和对数字的比较。那么我们能不能把这个过程进行简化呢?二叉搜索树(Binary Search Tree)会给你答案。
GOOD NIGHT
二叉搜索树
如上文所述,二叉搜索树的特征是有序,且预先把数据进行分段存储,并且将中间节点作为树的根节点,那么我们会得到如下的二叉树结构,这里以数组[7,25,36,50,64,78,87]为例,创建出来的二叉树如下所示:
我们可以看出一些特征:对于每个节点而言,它的左孩子都小于它的父节点,而右孩子,都大于它的父节点。所以我们把这样的二叉树,叫做二叉搜索树(Binary Search Tree)。
二叉搜索树,重复利用了二分法的思想来查找数据,但有时由于构造树时产生问题,导致同样的数据插入顺序不一样,二叉搜索树就会变成如下结构:
从两张图的比较中我们不难看出,图一的树高度为3,也就是最多只需要3次比较,就能找出节点,而图二的树的结构会变成线性,需要7次比较,查找时的复杂度也会瞬间提升为O(n)。
所以为了解决二叉树不平衡的问题,人们又提出了新的数据结构,叫做平衡二叉搜索树(AVL树)。
GOOD NIGHT
平衡二叉搜索树
刚才我们提出了,要解决二叉树的不平衡问题,就需要一个可以在每次插入或者删除节点时,都可以做到自动平衡的二叉树结构,也叫平衡二叉搜索树。
平衡二叉搜索树的核心思想就是计算每个节点的平衡因子(balance factor),平衡因子的定义是一个节点的左孩子的高度减去其右孩子的高度,这里的高度(height)就是指从一个节点出发到达最远叶子节点所经过的最长路径。如我们上述的图一中,从根节点50,到达子节点36的高度即为2。
通过上面的概念,我们可以发现,当一个节点的平衡因子绝对值大于等于1时,树就不再平衡,所以这里平衡二叉树还包含了一个旋转的机制,在此不表,感兴趣的同学可以自行查找进行理解。
那么我们在讲了这么多之后,平衡二叉搜索树是不是就可以作为索引背后的数据结构来进行存储了呢?这里再使用一张图来直观说明一下:
我们前文提到了查找数据是走了硬盘的I/O操作,那么对于这棵二叉树来说,树的高度为5,最坏的情况下,需要查找5次。这就意味着,如果树的高度越高,那么硬盘的I/O次数也会越多。
所以我们有没有办法去降低树的高度呢?可以遵循这样的思路去思考,如果一个节点下不止2个子节点,而是多个,那么整体的高度就可以降低。比如我们将二叉树,改为三叉树:
这个时候,同样的节点数量,我们的树高度则降为了4,也就是说,最多需要4次I/O即可找到需要查找的内容。
所以我们可以将二叉树改为M叉树(M>2),即一个节点下有M个子节点,这样当数据量大小为N时,M叉树的高度将会远远小于二叉树的高度,这样就引申出了B树的概念。
GOOD NIGHT
什么是B树?
B树的英文全名为Banlance Tree,翻译过来为平衡多路搜索树,我们从上文中已知,当一个节点有多个子节点时,高度会下降,因此B树很适合用于查询,在文件系统和数据库系统中的索引,常常会使用B树来实现。在这里有一个概念纠正下,有些书中或博客会提到B-树,而实际上B-树和B树是同一种结构,只是翻译名称上存在一些误解。
B树的结构如图所示:
B 树作为平衡的多路搜索树,它的每一个节点最多可以包括 M 个子节点,M 称为 B 树的阶。每个节点中,都存储了关键字和子节点的指针。对于一个 100 阶的 B 树来说,如果有 3 层的话最多可以存储约 100*100* 100=100 万的索引数据。
B树的特性有以下几点:
1、所有节点关键字是按递增次序排列,并遵循左小右大原则。
2、树中的每个节点至多有M个子节点,即至多有M-1个关键字(二叉树有2个子节点和1个关键字)。
3、除根节点外,其他节点至少有M/2个子节点。
4、若根节点不是叶子节点,则根节点至少有2个子节点。
5、所有叶子节点均在同一层,所以B树是一个所有平衡因子均为0的多路查找树。
以上图为例,根节点中有2个关键字:17和35,以及3个子节点指针:P1、P2和P3。而且在第二层最左侧的子节点中,有2个关键字8和12,包含了3个子节点。子节点1中的关键字为3和5,都小于8,而子节点2中的关键字为9和10,大于8小于12,子节点3中的关键字为13和15,均大于12,都符合我们上述提到的特性。
如果我们使用B树进行查找关键字9,那么可以分为以下几步:
1、先与根节点进行比较,9小于17,那么我们可以得到指针P1,继续从子节点中进行查找;
2、在子节点中,9大于8而小于12,此时可以得到指针P2,继续往下查找;
3、在最后一层的子节点中,找出关键字9,结束查找。
可以看出,相较于平衡二叉树而言,B树查找时的磁盘I/O要少,整体查询效率也要高出很多。看到这里相信大家已经大致明白了索引背后的工作原理,B树已经很适合作为索引的算法。但实际上,在MySQL中,还会使用B+树索引,这又是为什么呢?
GOOD NIGHT
B+树的改进
相较于B树而言,B+树在两个方面又做出了改进和提升,一方面是查询的稳定性,另一方面是查询的效率更高。
B+树的结构如下图所示:
与B树相比,B+树的非叶子节点不保存具体的数据,而只保存关键字的索引,所有的数据都会保存至叶子节点。因为所有数据必须要到叶子节点才能获取到,所以每次数据查询的次数都一样,这样一来B+树的查询速度也就会比较稳定。
在B+树中,非叶子节点的子节点数=关键字数,如上图所示,根节点有2个关键字,对于的也有2个子节点。这样的好处是一个节点可以存储更多的关键字,阶数会更大,高度会更低,查询效率也就更高。
B+树查找关键字的方式与B树类似,先从关键字中找出范围和指针,然后找到对应的子节点,一级一级往下查询,直到最终的叶子节点查出所需要的数据。
由于B+树的数据都存储在叶子节点,所以更有利于数据库做全表扫描,不需要像B树一样逐层扫描,而是直接遍历所有的叶子节点即可。
GOOD NIGHT
总结
今天我们从最基本的二分查找开始,逐步分析了各种查找树的工作原理和优缺点,不断改进,从而挖掘出最终的B树和B+树算法,深刻理解了索引背后的工作原理。虽然传统的二叉树查询的效率也很高,但是很容易增加磁盘I/O的次数,影响索引使用的效率,所以我们最终会采用降低树高度的方式来构造索引。
在实际工作中,我们使用索引也许不会直接去编写B树和B+树的算法。但是学习这些算法,会有助于我们增强逻辑思考的能力,还可以提升一些设计方面的能力,对于“修炼内功”会很有帮助,希望大家可以在这条路上持之以恒。
下一期将是索引系列的最终篇章,我们会结合实际工作中的复杂SQL场景,合理使用索引和改写原有语句以避免全表扫描来对数据库进行优化,敬请期待!
您的点赞和在看是我创作的最大动力,感谢支持
公众号:wacky的碎碎念
知乎:wacky