从数据结构角度分析MySQL的索引选择
在聊这个问题之前我们先想一个问题:Mysql是怎么储存数据的?在Mysql中,服务端获取到一条sql语句后,会先对这条语句进行解析,检查是否有语法等错误,然后生成一个优化过的执行方案交给Mysql的执行器执行。而执行器会调用储存引擎的API,对数据进行实际的存储操作
。其实也就是说,Mysql自身是不会去做一个存储数据的操作的,他会将这个操作委托给储存引擎去执行。所以,为什么Mysql使用B+树做索引
这个问题实际上是在问Mysql的默认储存引擎为什么使用B+树做索引
。
InnoDB为什么使用B+树做索引?
我们都知道,Mysql5.5之后的默认储存引擎都是InnoDB,那InnoDB为什么选择了B+树呢?对数据的操作,无非就是增删改查这四种操作,我们可以从数据结构的角度去思考:
-
关于增删改
大家应该很容易想到链表这种结构是非常适合这些操作的
-
关于查找
对于一个有序序列来说,我们最快且最容易想到的查找方法就是二分查找
那么,有没有什么结构既是个链表,又使用了二分法的思想呢?没错,就是二叉查找树!
二叉搜索树
对于一颗二叉搜索树来说:
-
若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。
-
若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。
-
任意结点的左、右子树也分别为二叉搜索树。
-
对一颗二叉搜索树进行中序遍历可以得到有序数列。
这种结构很好地满足了我们的需求,我们每次搜索的时间复杂度为O(logn)
,时间的开销与我们构建出树的树高正相关。但是,大家都知道,在实际应用场景下,我们数据库一张表随随便便就存了几千几百条数据,而我们的二叉搜索树本质上只是一颗二叉树,在数据量多的情况下整棵树还是会变得很高,导致查找效率下降。而且大家可以想象一种极端的情况:每个节点的后一个节点的值都比当前节点大,这样就会导致整棵二叉搜索树退化成一个单链表( 如上图根节点的右子树所示,大家再脑补几个节点应该就能想明白 ),这样所造成的性能损耗是我们无法忍受的。
平衡二叉查找树
为了避免二叉查找树退化成单链表,又有大佬提出了平衡二叉查找树的概念。
在平衡二叉查找树中,在自身是一个二叉查找树的基础上,又有任一节点对应的两棵子树的最大高度差为1
。具体的实现就不在这里展开了,不是重点…
虽然解决了退化问题,但是他本质上还是一颗二叉树啊,节点一多还是会导致树很高,还是会查地慢。那我们有什么办法减少树高呢?
B树
当然有啦!用多叉树不就完事了。我们的B树正是这样一种结构,你可以将他简单理解为平衡多叉查找树
。
以下是一颗B树的简单示意图,红色是索引(主键),绿色是行数据。
特别说明一下,B数的阶数
指的是书中最多子节点的个数
,如图中就是一颗三阶B树。
怎样在B树中搜索?
假设我们要找索引为10
的数据:
- 从根节点开始,发现
10
< 17,走ptr1 - 发现 8 <
10
< 12,走ptr2 - 到叶子结点,直接遍历就找到索引为10的元素了
在实际的使用场景,我们通常会将阶数设置的比较大,Mysql在数据量为2000W左右时依旧能将树高维持在3左右,这样对查询效率无疑是一个很大的提高。
看起来B树已经比较完美了,那为啥InnoDB储存引擎还是使用了B+树来储存呢?
B+树
大家可以想想B树有啥缺点,我这里直接上结论了。
- 在B树中,每个节点都包含了索引和数据。我们在搜索的时候总得把这些节点数据从磁盘先读入内存中,然后再根据程序做一下判断,假设每一行的数据量都很大,那就意味着,我们在有限的内存里能读入的节点数量就少了,我们需要更频繁地与进行磁盘IO,这是非常耗时间的。
- 每次搜索都需要从根节点遍历。
那我们B+树有什么优势呢?
- B+树只在叶子节点存数据,其他节点都存索引。这样带来的好处就是,除了叶节点之外,其他节点占用的空间是比较小的,我们一次磁盘IO可以处理更多的节点。
- B+树的叶子节点之间可以看做是一个双向链表。这种设计可以让我们不用每次搜索都从根节点遍历,更有利于我们做范围查询,范围查询也是我们sql中比较常见的,比如
查年龄在18到20岁间的学生姓名
,那我们只需要从根节点开始,找到第一个年龄是18岁的学生后,直接通过指针向后遍历就行了,不需要再从根节点开始找下一个。
哈希表
我们知道哈希表的查找速度也是很快的,时间复杂度为O(1)
,但是他只能一个一个查,没有办法实现范围查询。
历就行了,不需要再从根节点开始找下一个。
哈希表
我们知道哈希表的查找速度也是很快的,时间复杂度为O(1)
,但是他只能一个一个查,没有办法实现范围查询。