Mysql的索引深度讲解

数据页的存储结构:

数据库最终所有的数据都是存储在磁盘上的文件里的,然后在文件里存放的物理格式就是数据页,那么大量的数据页在磁盘文件里是如何存储的呢?

大量的数据页是按顺序一页一页存储的,然后两两相邻的数据页之间采用双向列表的格式相互引用,如下图:

那么这个图在磁盘文件中是如何存储的呢?

一个数据页在磁盘文件里就是一段数据,可能是二级制或其他特殊格式的数据,然后数据页里面包含两个指针,一个指向自己上一个数据页的物理地址,一个指向自己下一个数据页的物理地址。大概可以认为是下面这样子:

DataPage: xx=xx, xx=xx, linked_list_pre_pointer = 15367, linked_list_next_pointer = 34126 || DataPage: xx=xx, xx=xx, linked_list_pre_pointer = 23576, linked_list_next_pointer = 87234 || ...

每个数据页可以认为就是DataPge打头开始,直到||符号中的一段磁盘里的连续的数据。Linked_list_pre_pointer指向了上一个数据页的物理地址,linked_list_next_pointer指向了下一个数据页的物理地址。15367可以认为就是磁盘文件里的position或者offset。

然后一个数据页里面会存储一行一行的数据,然后数据页里面的每一行数据都会按照主键大小进行排序存储,同时每一行数据都有指针指向下一行数据的位置,组成单向列表,如下图:

全表扫描:

每个数据页中都会有一个页目录,里面根据数据行的主键存放了一个目录,同时数据行是分散存储到不同的槽位里去的,所以每个数据页的目录里,就是这个页里每个主键跟所在槽位的对应关系,如下图所示:

如果数据量用一个数据页就可以容纳,那么查询很简单,到数据页的页目录中根据主键进行二分查找,找到对应的槽位,然后遍历槽位中的每一行数据,就能快速找到主键对应的数据了。

但如果是根据非主键的字段进行查找,就没办法利用数据页中的页目录了,只能进入到数据页里,根据单向链表依次遍历,性能很差。

如果数据量很多,需要很多数据页来存储,那么此时,就需要遍历数据页了:

根据数据页的双向链表,从第一个数据页开始遍历所有数据页。从一个数据页开始,得把数据页从磁盘读取到内存的buffer pool中的缓存页中来,如果是主键就根据页目录中进行二分查找定位槽位,如果是非主键,只能一个一个遍历数据行。

如果第一个数据页没有找到,那么就沿着双向链表去找下一个数据页,然后读取到buffer pool的缓存页中去,然后按一样的方法继续找:

最坏的情况是,你得把所有数据页中的每一行数据全部遍历一遍,才能找到你要的数据,这就是全表扫描!

一般来说,没有索引的情况下,所有的数据查询,其实在物理层都是全表扫描。

页分裂:

我们在往表里插入数据的过程中,会涉及到一个页分裂的过程,也就是说表里是如何出现一个又一个数据页的。

一个数据页的容量是有限的,默认为16KB,当数据量多的时候,自然会发生页分裂。

同时页分裂过程中才会涉及到数据的挪动,尤其是主键为UUID的时候。因为数据页必须保证的一点是:上一个数据页的最大值小于下一个数据页的最小值

思考:UUID为主键时,触发页分裂的条件是什么?主键自增时,触发页分裂的条件又是什么?

主键索引:

现在是这样的,假设我们有多个数据页,然后我们想要根据主键来查询数据,那么直接查询的话也是不行的,因为我们也不知道主键到底是在哪里,是不是?

比如下图:

现在假如要搜id=4的主键,你怎么知道它在哪个数据页里面?假设还是这个样子的话,也就只能全表扫描了。

所以此时,就需要针对主键设计一个索引了。就是创建一个主键目录,把每个数据页的页号,还有数据页里的最小主键值放在一起,组成一个目录,如下图:

现在有了这个主键目录,就可以直接到主键目录中去搜索,比如你要找id=3的数据,那么可以直接定位到数据页2。如果有很多数据页,主键目录中有很多条记录,那么此时就可以根据二分法去查找到你要找的数据在哪个数据页里。

B+树:

上面示例中的主键目录实际上也是放在数据页中的,我们可以认为是索引页,因为里面存放的都是索引。

那么如果你的表里有几百万、几千万的数据,那么此时,一个索引页肯定放不下了,会有很多的索引页,如下图:

那么上一次的那个问题又出现了,我该到那个索引页里面去找呢?

于是,我们可以把索引页多出来一个层级,在更高层级的索引页中,保存了每个索引页页号和索引页里的最小主键值,如下图:

现在好了,我们可以从最上层的索引页开始,一层一层往下找,最终定位到某个数据页,然后根据页目录定位到某个槽位。

随着数据量的继续增大,如果最底层的索引页也放不下那么多数据了的话,那么就会出现更高层次的索引页。一层一层组成一个树,也就是我们常说的B+树。

当你为一个表的主键建立起索引之后,其实这个主键的索引就是一颗B+树,然后当你要根据主键来查找数据的时候,直接就是从B+树的顶层开始,进行二分查找(B+树的每个节点,也就是每一个索引页,都是有很多条数据的,所以需要进行二分查找,定位到下一层的索引页或数据页地址),一层一层往下定位,最终一直定位到数据页里,在数据页的页目录中进行二分查找,找到那条数据。

聚簇索引:

最下层的索引页,都会有指针引用数据页:

另外,索引页自己内部,对于一个层级的索引页,互相之间都是基于指针组成双向链表的:

从根节点的索引页开始,直到所有的数据页,就这样组成了一个巨大的B+树。

如果一个B+树的数据结构里,叶子节点就是数据页本身,那么此时,我们就称这颗B+树索引为聚簇索引!

在InnoDB存储引擎中,你对数据的增删改的时候,就是直接把你的数据页放在聚簇索引里的,聚簇索引包含了数据。

如果你的数据页进行了分裂,此时会调整各个数据页内部的行数据,保证数据页内部的主键值都是有顺序的,保证下一个数据页的所有主键值大于上一个数据页的所有主键值。同时在数据页分裂的时候,会维护你的上层索引数据结构,在上层索引页里维护你索引条目。

如果你的数据页越来越多,一个索引放不下,此时就会新拉出来一个索引页,同时再搞一个上层的索引页。

按照这个顺序,依次类推,如果你的数据量越大,此时就会多出更多的索引页层来。不过,即使你是亿级的大表,索引也就三四层而已。

聚簇索引默认是按照主键来组织的。你再增删改数据的同时,会维护聚簇索引。

二级索引(非聚簇索引):

对主键之外的其他键建立的索引,叫做二级索引。

假设你要对其他键建立索引,那么会为这个索引再单独创建一颗B+树。在你插入数据的时候,不仅需要维护聚簇索引,同时还会维护这个二级索引的B+树。

比如你基于name字段建立了一个索引,那么此时你插入数据时,就会重新搞一颗B+树,B+树的叶子节点也是数据页,但是这个数据页里只存放主键值和name值,如下图:

在这个nam索引的B+树里,叶子节点中的数据页中的数据的排序是按name值进行的!上层的索引页也是按照name值进行排序的:

假设要根据name值进行搜索,那么和根据主键在聚簇索引中搜索的过程是一样的。

找到对应的叶子节点,只能找到对应的主键值,没有其他数据。要想获取其他数据,还需要根据主键值去“回表”:

这个回表,其实就是根据主键值,再去聚簇索引中去查找一遍。

Mysql内部架构_jerry_dyy的博客-CSDN博客

Mysql存储模型_jerry_dyy的博客-CSDN博客

InnoDB内部架构_jerry_dyy的博客-CSDN博客

Buffer Pool 核心原理_jerry_dyy的博客-CSDN博客

Buffer Pool生产实践_jerry_dyy的博客-CSDN博客

Mysql事务隔离机制_jerry_dyy的博客-CSDN博客

Mysql的锁机制_jerry_dyy的博客-CSDN博客

Mysql的索引深度讲解_jerry_dyy的博客-CSDN博客

Mysql索引的使用_jerry_dyy的博客-CSDN博客

SQL语句的执行计划_jerry_dyy的博客-CSDN博客_sql语句的执行计划

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值