从B+树到MySQL索引

从B+树到MySQL索引

(一)为什么是B+树

我们先来说一下什么是索引。这个东西应该没有人没听说过,也没有人不会用,那就简单讲一下概念:索引是一个帮助数据库高效获取排好序的数据的数据结构。

许多人面试的时候可能会被问到这样一个问题:Mysql数据库的索引的存储结构是什么?我相信百分之百的人都会回答说:是B+树!确实,因为这是一个非常非常基础的问题。但是,如果面试官深入问一下,能把背后原理说清楚的人应该是屈指可数,下面我们来了解为什么一定是用B+树,而不是B树、二叉搜索树、红黑树之类的。

由于这个专题是讲MySQL数据库系统,因此关于数据结构的东西我只能点到为止,大概讲讲概念(不然文章的篇幅会太长,小伙伴们可能看的会头晕),因此对数据结构不熟悉的朋友可以自行询问Google。

1. 为什么不用二叉搜索树

二叉搜索树是非常常见的一种数据结构,它实际上就是一个左子节点的值小于父节点,右子节点的值大于等于父节点的一棵特殊的二叉树。

在这里插入图片描述

图中这样就是一个二叉搜索树了,我们可以发现他满足我们上面的定义。我们假设有一张表,名table,其中表的id字段设置了索引(如上图),当我们需要查询一条特定的数据时,比如说id为5的(select * from table where id = 5),我们只需要三次比较就能找到(分别和3、6、5作比较),这比直接的遍历全表肯定要高效很多。那么问题来了,既然这个数据结构既简单又好用,为啥不能作为数据库的索引的数据结构呢?我们可以看一个特殊情况:

在这里插入图片描述

当我们连续插入多条数据,并且这些数据的id恰好是递增,则会出现上面这种情况。很明显,二叉搜索树歪到了一边,导致这个二叉树退化成了链表。当我们用这个二叉搜索树来查询时,他的效率和直接遍历全表没有任何区别,这也就失去了索引存在的意义。换句话说,二叉搜索树越不平衡,查询的效率越低。这也就是索引不用二叉搜索树来作为数据结构的原因。

2. 为什么不用红黑树

红黑树是二叉搜索树的一种变体。他的最大特点是自动平衡(但不像二叉平衡树那样的绝对的平衡,二叉平衡树就是因为追求绝对平衡导致删除元素的效率极其低下而几乎无人使用),也可以说他是一个能够自动保持相对平衡的二叉搜索树,他依旧满足二叉搜索树的基本定义。上面那个歪到一边的二叉搜索树我们可以用红黑树重新插入数据试试,看看有什么变化。

在这里插入图片描述
我们发现,同样是依次插入了1、2、3、4、5五个数据,红黑树却没有完全歪到一边,而是以一个比较正常的形态展现出来。这也就是为什么人们说红黑树可以自动平衡的原因。既然他已经完美解决了二叉搜索树的弊端,那为什么也不能作为索引呢?

众所周知,实际生产环境用的数据库表的记录可能多达上千万行,如果用红黑树来保存索引,那么树的高度将会变得非常大。假设表中有一千万行数据,那么树的高度就是24层,也就是说我们如果要查询位于最下面一层的数据,需要比较24次,每次比较都意味着读取一个节点的值,也就意味着一次磁盘IO(这里暂时不考虑多个节点碰巧处于同一块内),显然是效率很低的。因此我们发现,当树的高度不断增加,查询的效率也就随之降低了,因此我们需要一个更高效的解决方案,尽可能的减少磁盘IO的次数。

3. 为什么不用B树

红黑树在数据量大的时候由于树的层数太多被PASS了,那么B树和红黑树相比有什么优势呢?首先,层数过多的问题我们需要解决,而最好的解决方案莫过于横向扩展,比如红黑树那张图中,如果一个节点中可以存储多个关键字,不就解决了层数过多的问题吗?

在这里插入图片描述

在B树中,内部(非叶子)节点可以拥有可变数量的子节点(数量范围预先定义好)。当数据被插入或从一个节点中移除,它的子节点数量发生变化。为了维持在预先设定的数量范围内,内部节点可能会被合并或者分离。因为子节点数量有一定的允许范围,所以B树不需要像其他自平衡查找树那样频繁地重新保持平衡,但是由于节点没有被完全填充,可能浪费了一些空间。子节点数量的上界和下界依特定的实现而设置。

由此可见,B树相对于红黑树最大的不同是:每个节点包含的关键字增多了,特别是在B树应用到数据库中的时候,数据库充分利用了磁盘块的原理(磁盘数据存储是采用块的形式存储的,Linux系统中默认每个块的大小为4K,每次IO进行数据读取时,同一个磁盘块的数据可以一次性读取出来)把节点大小限制并充分使用在磁盘块大小范围。同时,把树的节点关键字增多后树的层级就比红黑树少,减少数据查找的次数。

同样是一千万行数据,红黑树要24层,而B树只要4层就足够了(假设块大小为4K,节点大小为4B),差距整整六倍!

B树的优势除了树的高度小,还有对访问局部性原理的利用。所谓局部性原理,是指当一个数据被使用时,其附近的数据有较大概率在短时间内被使用。B树将键相近的数据存储在同一个节点,当访问其中某个数据时,数据库会将该整个节点读到缓存中。当它临近的数据紧接着被访问时,可以直接在缓存中读取,无需进行磁盘IO。换句话说,B树的缓存命中率比红黑树更高。

在这里插入图片描述

那为什么不用B树而要用B+树呢?因为B+树比B树更加优秀!

4. 选用B+树的原因

在这里插入图片描述

我们可以发现,与B树不同,B+树将数据全部放到了叶子节点中,而非叶子节点不再需要存放数据,只需要存储一些冗余的关键字维持树的结构即可。所有叶子节点以双向链表的形式相互连接,简化了访问过程。

我们分析一下B+树的主要优势:B+树的非叶子节点并没有包含具体信息(图中的data),因此其非叶子节点中的每个关键字所占空间相对B树更小。如果把同一非叶子节点的所有关键字存放在同一个块中,那么块所能容纳的关键字数量也越多,一次性读入内存中的关键字也就越多,相对来说IO读写次数也就降低了。与此同时,同一个非子节点的关键字数量的增加可以进一步降低树的高度(因为树的度数增大了),也可以降低IO的次数。

另外,B+树更适合做范围查询。在B树中进行范围查询时,首先找到要查找的下限,然后对B树进行中序遍历,直到找到查找的上限。而B+树的范围查询,只需要对链表进行遍历即可。同时,因为B+树查询实际是对链表进行遍历查询,因此有更稳定的查询复杂度,B树的查询时间复杂度在1到树高之间(分别对应需要的记录在根节点和叶节点两种情况),而B+树的查询复杂度则稳定为树高,因为所有数据都在叶节点。

我们来总结一下相比较B树,B+树具有的三条优势:更少的磁盘IO、更高的范围查询效率、更稳定的查询复杂度。

(二)聚簇索引与非聚簇索引

在《数据库原理》一书中是这么解释聚簇索引和非聚簇索引的区别的:聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。简单来说,聚集索引与非聚集索引的区别是:叶节点是否存放一整行记录。

那我们熟知的两种存储引擎:InnoDB和MyISAM,分别使用了哪种索引呢?InnoDB的主键使用的是聚簇索引,MyISAM不管是主键索引,还是二级索引使用的都是非聚簇索引。

在这里插入图片描述
对于聚簇索引表来说(左图),表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容)。

对于非聚簇索引表来说(右图),表数据和索引是分成两部分存储的,主键索引和二级索引存储上没有任何区别。使用B+树作为索引的存储结构,所有的节点都是索引,叶子节点存储的是索引和索引对应的记录的数据。

下面我们分别讲一下两种存储引擎中的索引的一些细节问题:

1. InnoDB

在这里插入图片描述
在InnoDB中,表数据文件本身就是按B+树组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主键索引。

因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

在这里插入图片描述
InnoDB二级索引的叶子节点中存储的不是“行指针”,而是主键值,并以此作为指向行的“指针”。好处是,InnoDB在移动行时无须更新二级索引中的这个“指针”,减少了当出现行移动或者数据页分裂时二级索引的维护工作。

在这里插入图片描述
InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。

在这里插入图片描述

在这里插入图片描述

3. MyISAM

MyISAM的叶子节点中保存的实际上是指向存放数据的物理块的指针。从MYISAM存储的物理文件看出,MyISAM引擎的索引文件(.MYI)和数据文件(.MYD)是相互独立的,索引文件仅仅保存数据记录的地址。

在这里插入图片描述
MyISAM的primary key和辅助索引没有任何区别。只是Primary key要求key唯一非空,而辅助索引的key可以重复。

在这里插入图片描述
MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。无论是主键索引还是二级索引都是如此。

在这里插入图片描述
在这里插入图片描述

(三)总结

简单总结一下,索引一般以文件形式存在磁盘中(也可以存于内存中),存储的索引的原理大致概括为以空间换时间,数据库在未添加索引的时候进行查询默认的是进行全量搜索,也就是进行全局扫描,有多少条数据就要进行多少次查询,然后找到相匹配的数据就把他放到结果集中,直到全表扫描完。而建立索引之后,会将建立索引的KEY值放在一个B+树。因为B+树的特点就是适合在磁盘等直接存储设备上组织动态查找表,每次以索引进行条件查询时,会去树上根据key值直接进行搜索。

聚簇索引和非聚簇索引也很好理解,只要记得一句话:聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。

2020年8月15日

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 鲸 设计师:meimeiellie 返回首页