谈谈InnoDB的页结构和B+Tree索引

190 阅读7分钟

内容大纲

  • Compact、Redundant、Dynamic、Compressed
  • 数据页的组成结构
  • 记录在页中的存储
  • B+Tree
  • 聚簇索引
  • 二级索引/非聚簇索引
  • 联合索引
  • 索引的注意问题
  • 回表、回表优化、索引下推等

数据在磁盘上的记录存放格式也叫行格式,InnoDB提供了4种行格式,其中Compact是最通用的一种。

1.Compact行格式

image.png

1.1 变长字段长度列表

例如varchar(int i) 类型的字段列就是变长的,在记录中会单独记录这个变长字段的信息放在变长字段的长度列表中,而且是根据列的顺序逆序排列。如果没有列是变长的,这个列表就不存在。

1.2 溢出列

如果某一列中的数据很多,那么该记录的真实数据处只会存储该列前768字节的数据,以及指向其它页地址的指针。存储这指针指向的数据的页称之为溢出页

2.Dynamic行格式

MySQL5.7默认的行格式,它和Compact行格式类似,只不过在处理溢出列的手段不同,它是将溢出列的所有数据都通过指针指向新的页面,只在该列的数据位置存放20字节的信息(指针+字节数)

3.Compressed行格式

和Dynamic行格式类似,只不过它支持页面压缩的功能,空间更小。

InnoDB管理数据的单位是页,一页默认大小是16KB,InnoDB有多种页,包括数据页、undo页、change buffer页等等。

4.数据页的结构总览

image.png

5.记录在页中的存储和排列

image.png 用户的数据记录存放在User Records的部分,一个页生成的时候是没有User Records的,当有记录需要插入进来的时候通过去Free Space申请空间,如果足够的话那么就会开辟User Records空间作为记录空间。如果Free Space的空间不够了,就会申请新的页。 用户数据的记录在User Records中是密集无缝隙的排列,且按照主键的大小从小到大单向链表排列。

6.Infimum记录和Supremum记录

InnoDB在设计时,在为每个页都分配了2个内置的记录,这个记录并不是用户真实记录。 Infimum记录表示一个页中最小的记录。 Supremum记录表示一个页中最大的记录。 也就是说在一个页里,没有任何一个真实记录小于Infimum,没有一个真实记录大于Supremum。

7.记录的行格式

image.png

7.1 delete_flag 删除标志位

这个记录属性在记录头中代表该记录是否被删除,占用1个bit位。InnoDB删除数据并不是立即从磁盘上抹掉,而是设置标志位然后将记录放入垃圾链表,这个被标记了删除的记录此时仍然占据着空间,后续如果有新的记录插入,且主键大小在这里,那么就会覆盖。

7.2 next_record 下一个记录相对指针

next_record表示当前记录的真实数据位置到下一条记录真实数据位置的相对偏移量,注意不是记录的相对位置,而是记录里面真实数据的相对位置。 由于记录的可变长、null值列表等信息是逆序存放,查找的时候有更高的高速缓存命中率。

8.记录的存放方式

image.png 记录在user_records空间中通过单向链表来串联存放。

9.Page Directory 页目录

因为记录是链表形式存放,如果遍历的时候最坏的情况时间复杂度是O(n),为了提高检索的效率,InnoDB采用了目录 + 二分法来提高数据查询效率。页目录就是一个单独提取出来的记录目录。 :InnoDB将数据记录按4~8个为一组,提取出最大记录的偏移量存放到页目录中,加快检索速度,通过二分查找法快速定位。

10.Page Header 页头

记录页的元数据信息,例如页的记录数、页目录有多少个槽、页的类型、页在B+Tree的层级等信息。

11.File Header 文件头

记录当前页的上一个页、下一个页的页号、页属于哪个表空间、页号等信息。 页号:唯一标识一个页的ID,不重复。 页的排列:通过文件头的指针,双向链表关联。

12.File Trailer 文件尾

用来校验数据页的完整性,固定8字节长度。数据页刷回磁盘时,为了保证页的完整性。 文件头和文件尾都存放着校验信息,因为文件头先刷入磁盘,这样可以校验文件头和文件尾的校验和是否一致来判断页是否完整。

13.B+Tree索引

image.png

13.1 索引检索的过程(检索ID = 8)

     1. 从根页面开始,通过二分查找的方法定位到页30。
     1. 因为 5 < 8 < 12,所以定位到页28。
     1. 通过页目录,二分查找链表找到ID = 8的数据。

13.2 页的创建、分裂、回收

页的大小是固定的,不能无限制的存放数据记录,如果页满了,而且还有数据的插入,那么就会触发新页的创建以及分裂、回收等操作。频繁的页分裂、整合等操作会极大降低MySQL的索引性能。

14.聚簇索引

主键索引也叫聚簇索引,叶子节点存放了完整的数据记录,一个叶子节点就是一个页,叶子节点之间通过双向链表结合。

15.非聚簇索引 / 二级索引 / 普通索引

和主键索引类似采用B+Tree形式构建,只不过二级索引的叶子节点没有存放完整你的数据记录,而是存放的当前索引值和主键ID值,通过回表去主键索引查询用户记录其它的信息。如果当前索引已经包含了要查询的信息,那么就不用回表,这就是覆盖索引

16.联合索引

多个列组成的索引称之为联合索引,联合索引的特点就是依次排序比较,例如index(A, B)。则是先比较A的值,然后再比较B的值。并且使用联合索引要遵守最左匹配原则,例如这个索引可以拆分为A、AB这两个,单独查询A是可以的,但是条件里只有B查询,则是不能命中索引的,因为无法得知A的条件。

17.索引的一些注意点

17.1 根页面

当一个表建立的时候,MySQL就会为其分配一个根页面,页号就固定好了不会再更改,并且这个根页面的信息会写入系统的字典表里,当做数据字典成为表的元数据信息存。

17.2 一个页至少存放2条记录

InnoDB为了让数的层高得到控制,规定每一个页必须至少存储2个以上的记录,如果存在超大记录,则根据行格式进行溢出页等处理。

17.3 回表的优化

     - 如果要按照排序查询,执行计划认为回表异常频繁,MySQL更加倾向全表扫描+文件排序方式进行查询。
     - `MRR 多范围读取`:先读取一部分普通索引中的主键ID,然后将其主键ID排序,统一批量回表,但是这个MRR优化措施的条件比较苛刻。

17.4 Descending Index 混合索引排序技术

MySQL8.0开始支持这个技术,支持联合索引的不同列自定义控制升序还是降序。避免了ASC和DESC混用带来的文件排序开销。

17.5 索引列尽量小、离散程度尽量高

17.6 前缀索引

MySQL可以为一些字符类型的列进行简历前缀索引,只需要将字符串的一部分前缀放入索引中即可。

17.7 索引下推

在MySQL5.6以后,默认开启索引下推技术。 索引下推:在联合索引中,如果不开启索引下推,那么当命中A索引的时候会通过回表去主键索引中拿到所有信息,然后比对B的值。如果开启了索引下推,在回表之前会进行B的判断。