MySQL底层为什么要选用B+树作为索引的数据结构呢?

Posted c.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL底层为什么要选用B+树作为索引的数据结构呢?相关的知识,希望对你有一定的参考价值。

mysql底层为什么要选用B+树作为索引的数据结构呢?

MySQL我想是大家平时都会用到的数据库了,我们平时优化SQL的执行速度往往都是加索引就完事了,管他的看执行计划然后加索引。那我们想过我们加索引其实加的是什么东西吗,为什么加索引能够帮我们优化查询速度呢。其实这就跟查字典一个道理了,如果我们想查一个字,在没有目录的情况下,我们就需要一页页翻,直到翻到对应的页数。但是如果我们有目录,我们就可以通过查看目录知道这个字对应的页数范围,然后在这个范围内去翻就能很快的找到我们想要查的字了。

所以索引的作用就是做数据的快速检索,而快速检索的实现本质就是数据结构。通过不同的数据结构的选择,从而实现各种数据的快速检索。。

所以在数据库中,实现一个高效的查找算法是非常重要的,因为数据库中往往有非常多的数据,一个高效的索引能帮我们省去大量的搜索时间。加入没有索引,在数据库中存储了1000W的数据,那我们查找一个id=1000W的数据,通过暴力顺序检索可能就需要1000W次,这个速度是不能接受的。那我们可以通过什么办法来优化我们的查询速度吗。

接下来我们就从多个数据结构下手,来看看MySQL选择了什么数据结构来实现索引。

MySQL 索引底层数据结构选型

哈希表(Hash)

一讲到Hash表我想大家都很熟悉了,在我们Java中的HashMap就是用到了Hash表这种数据结构了。Hash确实是数据快速检索的利器。

哈希算法:也叫散列算法,就是把任意值(key)通过哈希函数变换为固定长度的 key 地址,通过这个地址进行具体数据的数据结构。

Hash 索引是比较常见的一种索引,他的单条记录查询的效率很高。但是,Hash索引并不是最常用的数据库索引类型,尤其是我们常用的Mysql Innodb引擎就是不支持hash索引的。

主要有以下原因:

  • Hash算法有个数据碰撞的问题,也就Hash函数可能对不同的 key 会计算出同一个结果,比如 hash(7)可能跟 hash(199)计算出来的结果一样,也就是不同的 key 映射到同一个结果了,这就是碰撞问题。解决碰撞问题的一个常见处理方式就是链地址法,即用链表把碰撞的数据接连起来。计算哈希值之后,还需要检查该哈希值是否存在碰撞数据链表,有则一直遍历到链表尾,直达找到真正的 key 对应的数据为止。
  • Hash索引适合精确查找,但是范围查找不适合
    因为存储引擎都会为每一行计算一个hash码,hash码都是比较小的,并且不同键值行的hash码通常是不一样的,hash索引中存储的就是Hash码,hash 码彼此之间是没有规律的,且 Hash 操作并不能保证顺序性,所以值相近的两个数据,Hash值相差很远,被分到不同的桶中。这就是为什么hash索引只能进行全职匹配的查询,因为只有这样,hash码才能够匹配到数据。

总结:

使用哈希算法实现的索引虽然可以做到快速检索数据,但是没办法做数据高效范围查找,因此哈希索引是不适合作为 Mysql 的底层索引的数据结构。

二叉查找树(BST)

一整起数据结构就真的拉跨,又得来重新回顾一下什么是二叉树,还有二叉查找树了。
不懂的小伙伴最好先去看看数据结构,不然对后面知识的理解还是有一定的难度的,我已经在恶补了…

讲二叉查找树之前,我们先讲一下什么是树还有什么是二叉树,先有一个基础的概念。

我们先来说一下什么是树,概念太多我们就不多看了,想了解的可以看文章最下方的参考链接,我这里就借用一个图来展示,自己标识了一些内容。

在这里插入图片描述

那我们再来看看什么是二叉树。

在这里插入图片描述

二叉查找树是一种支持数据快速查找的数据结构,二叉查找树的时间复杂度是 O(logn)

在这里插入图片描述

从图上看,我们要找到6这个结点,我们只需要比较3次就可以了。此外二叉树的结构能不能解决哈希索引不能提供的范围查找功能呢?

答案是可以的。观察上面的图,二叉树的叶子节点都是按序排列的,范围查找也算是比较容易实现。

但是普通的二叉查找树有个致命缺点:极端情况下会退化为线性链表,二分查找也会退化为遍历查找,时间复杂退化为 O(N),检索性能急剧下降。比如以下这个情况,二叉树已经极度不平衡了,已经退化为链表了,检索速度大大降低。如下图。

在这里插入图片描述

在数据库中,数据的自增是一个很常见的形式,比如一个表的主键是id,而主键一般默认都是自增的,如果采取二叉树这种数据结构作为索引,那上面介绍到的不平衡状态导致的线性查找的问题必然出现。因此,简单的二叉查找树存在不平衡导致的检索性能降低的问题,是不能直接用于实现 Mysql 底层索引的。

平衡二叉树(AVL树)

在这里插入图片描述

在这里插入图片描述

由上图可知,同样的结点,由于插入方式不同导致树的高度也有所不同。特别是在带插入结点个数很多且正序的情况下,会导致二叉树的高度是O(N),而AVL树就不会出现这种情况,树的高度始终是O(logn).高度越小,对树的一些基本操作的时间复杂度就会越小。这也就是我们引入AVL树的原因.

在这里插入图片描述

如果对平衡二叉树有兴趣,想了解更多的可以参考最下方的参考资料,这里就不占用太多的篇幅去讲了,一时半会也将不清楚哈哈哈。

红黑树

虽然平衡树解决了二叉查找树退化为近似链表的缺点,能够把查找时间控制在 O(logn),不过却不是最佳的,因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入/删除节点的时候,我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。

显然,如果在那种插入、删除很频繁的场景中,平衡树需要频繁着进行调整,这会使平衡树的性能大打折扣,为了解决这个问题,于是有了红黑树,红黑树具有如下特点:

  1. 具有二叉查找树的特点。
  2. 根节点是黑色的;
  3. 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存数据。
  4. 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的。
  5. 每个节点,从该节点到达其可达的叶子节点是所有路径,都包含相同数目的黑色节点。

在这里插入图片描述

正是由于红黑树的这种特点,使得它能够在最坏情况下,也能在 O(logn) 的时间复杂度查找到某个节点。这里就不占用太多篇幅去解释了(我也在努力花时间看这个红黑树,还是蛮复杂的…)。

不过,与平衡树不同的是,红黑树在插入、删除等操作,不会像平衡树那样,频繁着破坏红黑树的规则,所以不需要频繁着调整,这也是我们为什么大多数情况下使用红黑树的原因。

不过,如果你要说,单单在查找方面的效率的话,平衡树比红黑树快。所以,我们也可以说,红黑树是一种不大严格的平衡树。也可以说是一个折中发方案。所以为什么Java8中HashMap会选用了红黑树来解决哈希冲突导致退化为近似链表的问题了。

所以在这里总结一下AVL树和红黑树

  • 不错的查找性能O(logn),不存在极端的低效查找的情况。
  • 可以实现范围查找、数据排序。

但是 AVL 树和红黑树并不适合做 Mysql 数据库的索引数据结构,因为考虑一下这个问题:
数据库查询数据的瓶颈在于磁盘 IO,如果使用的是 AVL树或者红黑树,我们每一个树结点只存储了一个数据,我们一次磁盘 IO 只能取出来一个节点上的数据加载到内存里。那如果我们要存储海量的数据呢?
可以想象到二叉树的节点将会非常多,高度也会极其高,我们查找数据时也会进行很多次磁盘 IO,我们查找数据的效率将会极低。

所以我们设计数据库索引时需要首先考虑怎么尽可能减少磁盘 IO 的次数。

磁盘 IO 有个有个特点,就是从磁盘读取 1B 数据和 1KB 数据所消耗的时间是基本一样的,我们就可以根据这个思路,我们可以在一个树节点上尽可能多地存储数据,一次磁盘 IO 就多加载点数据到内存,这就是 B 树,B+树的的设计原理了

B-树

那我们首先来讲一下B-树,我首先纠正一下这个叫B树,不是B减数,没有B减数,中间是一个横杆,所以B-树,我们直接读B树就行了,别被误导了。

B-树全称Balance-tree(平衡多路查找树),平衡的意思是左边和右边分布均匀。多路的意思是相对于二叉树而言的,二叉树就是二路查找树,查找时只有两条路,而B-tree有多条路,即父节点有多个子节点。

B树是一种平衡的多分树,通常我们说m阶的B树,它必须满足如下条件:

  • 每个节点最多只有m个子节点。
  • 每个非叶子节点(除了根)具有至少⌈ m/2⌉子节点。
  • 如果根不是叶节点,则根至少有两个子节点。
  • 具有k个子节点的非叶节点包含k -1个键。
  • 所有叶子都出现在同一水平,没有任何信息(高度一致)。

上面讲到了很多的概念,下面我们就来一一解答一下。

  • 第一点,是什么是B树的阶 ?

    B树中一个节点的子节点数目的最大值,用m表示,假如最大值为10,则为10阶,如图下图就是一个4阶的B树

在这里插入图片描述

所有节点中,节点【13,16,19】拥有的子节点数目最多,四个子节点(灰色节点),所以可以定义上面的图片为4阶B树。

  • 第二点,什么是根节点 ?

    节点【10】即为根节点,特征:根节点拥有的子节点数量的上限和内部节点相同,如果根节点不是树中唯一节点的话,至少有俩个子节点(不然就变成单支了,因为需要满足高度一致,如果是单支的话高度就不一致了)。在m阶B树中(根节点非树中唯一节点),那么有关系式2<= M <=m,M为子节点数量(要>=2是因为如果是单支,高度就不一致了,为什么要<=m呢,因为m阶B树的最大子节点的个数就是m,比如上图中每个节点中拥有的子节点的范围就是2<= M <=4);根节点包含的元素数量 1<= K <=m-1,K为元素数量(元素数量就是每个节点中的数据个数,比如上图根结点的元素只有【10】,所以元素数量是1)。

  • 第三点,什么是内部节点 ?

    节点【13,16,19】、节点【3,6】都为内部节点,特征:内部节点是除叶子节点和根节点之外的所有节点,拥有父节点和子节点。假定m阶B树的内部节点的子节点数量为M,则一定要符合(m/2)<= M <=m关系式(比如上图的4阶B树,需要满足2<=M<=4);包含的元素数量 (m/2)-1<= K <=m-1,K为元素数量。m/2向上取整(比如上图4阶B树内部节点包含的元素数量为1<=K<=3)。

  • 第四点,什么是叶子节点?

    节点【1,2】、节点【11,12】等最后一层都为叶子节点,叶子节点对元素的数量有相同的限制,但是没有子节点,也没有指向子节点的指针。特征:在m阶B树中叶子节点的元素符合(m/2)-1<= K <=m-1,m/2向上取整。

关于B树的插入删除还有一些概念性的内容有不明白的小伙伴可以参考一下下面的文章,我觉得写得挺好的。

B树、B+树详解

面试官问你B树和B+树,就把这篇文章丢给他

总结:

B树是一种自平衡树数据结构,它维护有序数据并允许以对数据进行时间进行搜索,顺序访问,插入和删除。B树是二叉搜索树的一般化,因为节点可以有两个以上的子节点。与其他自平衡二进制搜索树不同,B树非常适合读取和写入相对较大的数据块(如光盘)的存储系统。它通常用于数据库和文件系统。从上图可以看出,B 树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子节点,
基于这个特性,B 树查找数据读取磁盘的次数将会很少,数据的查找效率也会比平衡二叉树高很多。

B+树

B+树是应文件系统所需而产生的B树的变形树,那么可能一定会想到,既然有了B树,又出一个B+树,那B+树必然是有很多优点的

B+树其实和B树是非常相似的,我们首先看看相同点。

  • 根节点至少一个元素
  • 非根节点元素范围:m/2 <= k <= m-1

不同点如下:

  • B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。
  • 因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。

在这里插入图片描述

总结:

通过 B 树和 B+树的对比我们看出,B+树节点存储的是索引,在单个节点存储容量有限的情况下,单节点也能存储大量索引,使得整个 B+树高度降低,减少了磁盘 IO。其次,B+树的叶子节点是真正数据存储的地方,叶子节点用了链表连接起来,这个链表本身就是有序的,在数据范围查找时,更具备效率。因此 Mysql 的索引用的就是 B+树,B+树在查找效率、范围查找中都有着非常不错的性能。

关于B+树想了解更多的可以参考一下:MySQL索引-B+树(看完你就明白了)

参考

面试必备之MYSQL索引底层原理分析

深入理解 Mysql 索引底层原理

MySQL底层实现机制

深入学习二叉树(一) 二叉树基础

图解数据结构树之AVL树

AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中?

为什么平衡二叉树也不适合作为索引

红黑树与AVL树,各自的优缺点总结

记一次腾讯面试:有了二叉查找树、平衡树(AVL)为啥还需要红黑树?

【漫画】以后在有面试官问你AVL树,你就把这篇文章扔给他。

红黑树(一)之 原理和算法详细介绍

动图演示:彻底理解红黑树?

B树、B+树详解

MySQL索引-B+树(看完你就明白了)

以上是关于MySQL底层为什么要选用B+树作为索引的数据结构呢?的主要内容,如果未能解决你的问题,请参考以下文章

mysql 索引底层

为什么选择B+树作为索引结构?

为什么选择B+树作为索引结构?

MySQL索引B+树在磁盘中的存储

Mysql-索引的底层结构

Mysql-索引的底层结构