MySQL索引B+树图解

393 阅读7分钟

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

一、无索引时的数据查询

数据表中的每行数据存放在数据区中,数据区中每行数据以单向链表的方式,通过指针连接起来

yuque_diagram (7).jpg 同时每个数据页之间再通过双向链表的方式组织连接起来

yuque_diagram (8).jpg

无索引时的数据查询:数据页开始存放在磁盘中,一张表一般会对应着多个数据页,查询数据时从磁盘中依次加载数据页到InnoDB的缓冲池中,然后对缓冲池中缓存页的每行数据,通过数据行的单向链表一个一个去遍历查找,如果没有找到,那么就会顺着数据页的双向链表数据结构,依次遍历加载磁盘中的其他数据页到缓冲池中遍历查询。

如果查的数据行在表的最后一个数据页的最后一行,所有的数据页都要被扫描一遍,然后每个数据页中也是遍历链表,整体的效果就是以O(n)的时间复杂度在链表了,查询的性能低。

优化数据页内查询效率--槽位
InnoDB存储引擎设计了槽位方式来组织数据页中的多个数据行,槽位信息存放在数据页中的数据页目录中。槽位就是将数据页中的多个数据行分组划分,每个数据行组都找这个组中的主键值最大的那个数据行的地址作为槽位的信息,这样数据页目录中的一个个槽位就是一个个目录,标记了多个数据行分组的位置信息。

yuque_diagram (9).jpg 有了数据页目录中的槽位信息,此时要查询数据页中的某行数据时,比如查询主键为4的数据,直接通过二分法以O(logn)的时间复杂度锁定数据页目录中的槽位2,因为槽位之间都是紧密连接的,可以通过槽位2找到槽位1,从槽位1末尾开始,对分组2中的数据开始遍历,因为每个分组中的数据量都少,此时在小的范围遍历下就可以快速找到主键为4的数据,时间复杂度从之前的O(n)降低到O(logn)效率。

如果不是通过主键查询,槽位还是使用不上,还得一个个遍历数据页中的单向链表去找到想要的数据行。

页分裂: 一个数据页16kb大小,当一个数据页中的数据行足够多时就会重新创建一个数据页继续写数据行,如果没有用到索引还好,但是如果在表中创建索引,那么对多个数据页中的数据就有约束。

如果新创建的数据页中的数据行的主键值,存在比它上一个数据页的主键值还小的情况,这种情况是不允许的。如果出现此情况,多个数据页之间的主键就无序了,而索引机制的实现是要基于多个数据页主键的大小是依次递增的,所以此时就会出现页分裂的情况。

其实页分裂目的就是调整下不同数据页的数据顺序,使得最终按顺序创建的索引页之间,后一个数据页中的每一个数据行的主键都要大于上一个数据页,当然一个数据页中当然是按照单向链表的方式依次递增。

频繁的页分裂情况下涉及到数据的移动,在性能上也是会损耗,在设计表结构时使用主键自增长的方式,使用主键自增长方式,能大大避免数据页之间主键大小出现顺序错乱的问题,减少页分裂发生的概率。

从主键目录到索引页:查询一行数据,在物理层面就是定位到哪一个数据页中的哪一行数据。在数据页中定位数据的问题,通过槽位的方式优化了在单个数据页中查询的效率,如何在大量的数据页中定位数据页的效率问题,这就是索引的目标。

主键目录:InnoDB存储引擎使用主键目录的方式,将数据页号和数据页最小的主键值作为一条记录

yuque_diagram (10).jpg 查询一条数据时,直接通过id去主键目录,通过二分查找定位到具体那个数据页,然后数据页内部通过定位槽位,遍历那个槽位对应数据行分组找到具体的一行数据。

索引页:每张表对应的数据页很多,主键目录就会有大量的数据,就有可能放不下,InnoDB设计想存放目录数据也是数据,就这样主键目录的信息就被移到数据页,这些数据页就被称为索引页。

yuque_diagram (11).jpg 一个数据页的大小也才16kb,索引页的容量也是有限,为了解决索引页容量不够的问题,索引页会重新创建和升级,先把超出容量的数据放到一个新的索引页中,然后再加一层索引页。

yuque_diagram (12).jpg 由索引页一层一层组成的结构就是索引树,在mysql中称之为B+索引树。可以使用二分法查询,所以查询一条数据,从树的根节点开始通过二分法查找,以O(logn)的时间复杂度锁定数据页,然后数据页中同样使用二分法锁定槽位,在槽位中遍历下找到数据。

聚簇索引、普通索引和覆盖索引:

一棵树,它的叶子节点是一个个数据页,这些数据页中存放的都是数据表中每一行的完整数据,所以说如果B+树是以完整数据的数据页为叶子节点的,把这个索引树称为聚簇索引;如果一个索引的索引树,叶子节点不是以数据页为叶子节点的,就称为二级索引 或普通索引。

聚簇索引和普通索引最大的区别是,聚簇索引的叶子节点存放了数据行的完整数据,而二级索引叶子节点只有数据的部分字段。

而覆盖索引本身并不是一种索引,而是一种查询数据的方式,直接通过二分法查询到结果,不需要再通过id值回表查询。

设计索引维度
1、首先从时间角度上:需要为了避免频繁的页分裂,需要尽可能使用主键自增长方式,保证新增的数据页中的数据行的主键都是递增,避免不必要的页分裂带来的性能损耗和拖慢查询效率。

另外选择合适的字段作为索引字段也很重要,需要选择基数较大的字段,也就是一个字段可能出现的值比较多,这样在B+树中查询进,才能最高效的发挥出二分法查询的威力,如果建立索引的字段基数比较小可能查询时二分法查找就会退化成时间复杂度为O(n)的线性查询

2、空间的角度上:因为索引数据本身也是要占空间的,可能选择字段长度较小的作为索引字段,这样B+树不至于那么占空间。

但是如果非得要以长字段作为索引也不是不行,可以采用折中的以字段的前缀作为索引,这样的索引也称为前缀索引,但是这样可能只能用在模糊查询上,用在group by 和order by上不太适合。

3、作用范围上:设计索引的目的,为了更好的用上索引,索引在设计时,尽可能让where、group by 、order by 这些语句都能用上索引。



二、思考

1、InnoDB的索引数据结构是什么?为什么用这种数据结构?
InnoDB索引的数据结构为B+树;B+树的特点是只有叶子节点带数据,而且默认是排好序的,支持二分法快速查找 ,且存放数据量也比较大,一般三层就可以存储20多个G数据了 ​