为什么Mysql使用B+树做索引?

76 阅读5分钟

InnoDB的索引数据结构

InnoDB为什么使用B+树做索引?

我们都知道,Mysql5.5之后的默认储存引擎都是InnoDB,那InnoDB为什么选择了B+树呢?对数据的操作,无非就是增删改查这四种操作,我们可以从数据结构的角度去思考:

  • 关于增删改

    大家应该很容易想到链表这种结构是非常适合这些操作的

  • 关于查找

    对于一个有序序列来说,我们最快且最容易想到的查找方法就是二分查找

那么,有没有什么结构既是个链表,又使用了二分法的思想呢?没错,就是二叉查找树!

二叉搜索树

对于一颗二叉搜索树来说:

  1. 若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。

  2. 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。

  3. 任意结点的左、右子树也分别为二叉搜索树。

  4. 对一颗二叉搜索树进行中序遍历可以得到有序数列。

image-20230422193254408.png

这种结构很好地满足了我们的需求,我们每次搜索的时间复杂度为O(logn),时间的开销与我们构建出树的树高正相关。但是,大家都知道,在实际应用场景下,我们数据库一张表随随便便就存了几千几百条数据,而我们的二叉搜索树本质上只是一颗二叉树,在数据量多的情况下整棵树还是会变得很高,导致查找效率下降。而且大家可以想象一种极端的情况:每个节点的后一个节点的值都比当前节点大,这样就会导致整棵二叉搜索树退化成一个单链表( 如上图根节点的右子树所示,大家再脑补几个节点应该就能想明白 ),这样所造成的性能损耗是我们无法忍受的。

平衡二叉查找树

为了避免二叉查找树退化成单链表,又有大佬提出了平衡二叉查找树的概念。

在平衡二叉查找树中,在自身是一个二叉查找树的基础上,又有任一节点对应的两棵子树的最大高度差为1。具体的实现就不在这里展开了,不是重点....

虽然解决了退化问题,但是他本质上还是一颗二叉树啊,节点一多还是会导致树很高,还是会查地慢。那我们有什么办法减少树高呢?

B树

当然有啦!用多叉树不就完事了。我们的B树正是这样一种结构,你可以将他简单理解为平衡多叉查找树

以下是一颗B树的简单示意图,红色是索引(主键),绿色是行数据。

image-20230422201403453.png

特别说明一下,B数的阶数指的是树中最多子节点的个数,如图中就是一颗三阶B树。

怎样在B树中搜索?

假设我们要找索引为10的数据:

  1. 从根节点开始,发现10 < 17,走ptr1
  2. 发现 8 < 10 < 12,走ptr2
  3. 到叶子结点,直接遍历就找到索引为10的元素了

在实际的使用场景,我们通常会将阶数设置的比较大,Mysql在数据量为2000W左右时依旧能将树高维持在3左右,这样对查询效率无疑是一个很大的提高。

看起来B树已经比较完美了,那为啥InnoDB储存引擎还是使用了B+树来储存呢?

B+树

大家可以想想B树有啥缺点,我这里直接上结论了。

  1. 在B树中,每个节点都包含了索引和数据。我们在搜索的时候总得把这些节点数据从磁盘先读入内存中,然后再根据程序做一下判断,假设每一行的数据量都很大,那就意味着,我们在有限的内存里能读入的节点数量就少了,我们需要更频繁地与进行磁盘IO,这是非常耗时间的。
  2. 每次搜索都需要从根节点遍历。

那我们B+树有什么优势呢?

  1. B+树只在叶子节点存数据,其他节点都存索引。这样带来的好处就是,除了叶节点之外,其他节点占用的空间是比较小的,我们一次磁盘IO可以处理更多的节点。
  2. B+树的叶子节点之间可以看做是一个双向链表。这种设计可以让我们不用每次搜索都从根节点遍历,更有利于我们做范围查询,范围查询也是我们sql中比较常见的,比如查年龄在18到20岁间的学生姓名,那我们只需要从根节点开始,找到第一个年龄是18岁的学生后,直接通过指针向后遍历就行了,不需要再从根节点开始找下一个。

哈希表

我们知道哈希表的查找速度也是很快的,时间复杂度为O(1),但是他只能一个一个查,没有办法实现范围查询。