mysql指引(八):innodb页结构

前言

上篇文章 中说到了Innodb页内的数据行结构,实际上什么都不需要记住,只需要知道数据行中有记录头,而记录头中有关键的信息,这些信息会和后续学习事务、锁等特性相关。

本篇,我们就来看下Innodb的页结构,实际上,前两篇文章中也或多或少的提到了页结构相关的内容。

页结构的意思就是:innodb以页这个逻辑概念为单位,将存储的数据和其他信息划分到一个个的页中。我们需要了解的就是这些页中的数据(泛指)是如何组织的。

页结构

页中可分为三类结构,即三块区域:

  1. 需要存储数据的结构
  2. 加快访问的数据结构
  3. 页自身的信息结构

而这三类结构就包含了下面七个部分,这七个部分,就是innodb页结构的划分,即页被划分成了下面7块:

  1. File Header
  2. Page Header
  3. Infimum 和 Supremum
  4. User Records
  5. Free Space
  6. Page Directory
  7. File Trailer

我们按照三种结构划分,来看看这七项内容。

需要存储数据的结构

假如是图书馆书架上的一排放书的空间,随着购入的书增多,这个空间会逐步被塞满。随着有些书被借出去,对应位置就会空出来,当这些书再还回来时又会被塞入相应的位置。

上面的描述,实际上展示了三条链表:

  1. 有书链表,即一本书的旁边是另外一本书,一个接着一个
  2. 空闲链表,即还可以放入新购入图书的空间
  3. 借走链表,即这些位置是被借走的书,后面还会再回来的。即可被重复使用。

对应到数据库这边,则是 User RecordFree Space

  1. 初始状态没有数据,即书架没书。实际上这段空间就是 Free Space
  2. 后来加入了数据,即书架新购入图书,则相当于占据了 Free Space 的一部分,这部分就是 User Record。前文所讲的数据行就构成了 User Record。
  3. 当某些数据被删除后,因为空间可以重复利用,所以程序内部会维护一个垃圾链表,来达到目的。

加快访问的数据结构

页之间的访问由B+树来实现快速访问,从而避免了大量链表的遍历,那么页的数据行之间的访问如何实现加速呢?假如有1万个数据行,难道要从头开始遍历下去吗?虽然数据页是被加载到内存中的,但是遍历这么多还是有点浪费的,能否加速呢?

Page DirectoryInfimum 和 Supremum 就是用来实现加速访问效果的结构。数据行之间是通过链表串起来的,那么 Infimum就相当于链表的头结点,Supremum就相当于链表的尾部结点。

所以 Infimum 的下一条记录就是第一个用户数据行数据,即 User Record 里面的第一个数据行;而 User Record 里面的最后一个数据行,其下一个记录就是 Supremum。


那么,这两个节点有什么意义呢?看起来好像没多大作用。是的,单独来看,确实没什么用处。但是和 Page Directory 结合起来则威力无穷。

通过前文的学习,我们知道下面两个知识点:

  1. 每个索引都是一棵B+树
  2. 每个页内的数据行是按照索引大小顺序来排序递增下去的

假如主键id的值为1~100,这一百个数全部存在一个数据页中。则有100个数据行,按照id=1 开始一直往下排到 id = 100.

我们要查询 id = 37 的数据,如果避免遍历前面36个节点呢?


innodb采用的方法是:

  1. 将这100个数据行进行分组,比如每10个为一组,然后将每组中索引值最大的数据行地址(偏移)记录下来。那么我们可以记录下,即如下id值对应的偏移:

10,20,30,40,50,60,70,80,90,100…

这些记录(id值和偏移)就放到 Page Directory 区域中。

  1. 当用户查找时,比如 id = 37,则首先去 Page Diretory中查找,利用的算法是二分法,首先找中间的数值,比如找到了 id = 50,比较发现 37 < 50,又因为数据行是按照顺序存放的,所以继续在

10,20,30,40,50

中寻找,最终可以找到 id = 30 的偏移。此时拿着偏移量再去 User Record 里面找对应的数据行,即直接找到了 id = 30的数据,30 < 37,继续向后遍历即可。

数据行越多,这样的处理方法带来的收益越大。

  1. 实际上,在数据插入的时候,innodb就在 Page Directory 中构造这些id了,这些也叫做 slot

因为最开始 User Record 中没有任何记录,所以 Page Directory 中也没有任何的 slot。如果此时插入一条数据,则 slot 该如何划分?所以在这种初始化的状态,Infimum 和 Supremum 就派上了用场。配合事先定义的划分规则,可以方便的去逐步构造出 Page Directory 中的各个槽,具体规则就不说了。知道 Page Directory 的作用即可。

页自身的信息结构

页自身的信息结构包含三个部分:

  1. File Header
  2. Page Header
  3. File Trailer

首先来说 File Trailer ,就是用来校验 页 中数据有没有损坏的,这个是可能出现的。比如从磁盘中读取数据时,突然断电等。所以必须要有校验,否则基于错误的数据源,后果不堪设想。

再然后是两个 Header,这其实和 数据行结构有点类似的感觉,数据行结构中也有 Header,是记录头信息,记录了数据行内部的一些信息,以及数据行与数据行之间的信息。

同理,这两个Header也记录了这两个方面的信息,我们就来分别看一下。


首先是 File Header,因为页有很多种类型,数据页只是其中之一。所以 File Header 中是各个页面通用的属性,比如上一页下一页的地址,该页是什么类型。其他的等遇到了再说,只要知道这个结构即可。

小结下:FIL_PAGE_PREV 字段就是上一页的地址,FIL_PAGE_NEXT 就是下一页的地址,FIL_PAGE_TYPE 就是页的类型。当然还有其他的,先不考虑。


其次,是 Page Header ,专门用来表示数据页的基本信息的。比如 slot 的数量等。

因为每个索引都是一棵B+树,而且可能会有很多索引,所以 Page Header 中还记录了这个页属于哪个索引;又因为每棵B+树的叶子节点(即最底层节点)才是存储数据的,其他层的节点都是用来快速定位到叶子节点的,所以还需要记录该页是否是叶子节点,或者说该页处于 B+树 中的哪一层次。

小结下:

PAGE_N_DIR_SLOTS 就是 slot 的数量,PAGE_LEVEL 就是该页在 B+树中的层次,PAGE_INDEX_ID 就是该页对应哪个索引。其他的,遇到了再记即可。

补充

页结构说完了,配合前两篇文章,这块应该是比较清晰了。现再补充几点:

  1. 我们提到过表空间,即 System tablespace 和 用户表的.ibd文件对应的表空间。

上文说到的 Page Header 中的 PAGE_INDEX_ID 只是一个数值,比如等于10,即该页对应索引ID为10的索引,那这个索引到底属于哪个字段呢?

还有前文提到的,数据行结构中并没有存储定长字段的长度,那么这些长度去哪里看呢?等等一些信息,实际上都是去表空间中看的,系统表空间中有一块存储的是Data Dictionary 即数据字典,这里面就放了这些信息。后面我们还会再次提到的。

  1. 一个数据表对应一个 .ibd 文件,该文件包含了这些页数据。

前文也整理了文件、页和真实数据的联系图。比如表空间大小为 1GB,页16KB,则总共有65536个页。所以 File Header 中还有一个字段FIL_PAGE_OFFSET 表示该页在表空间中的偏移值。先这样提一下。

  1. 每棵B+树都有一个根节点,实际上创建索引的时候,首先创建的就是根节点,然后随着数据的填充,才逐步变成了多层的B+树。

那么根节点一旦创建出来,其页号就不会变了。当innodb第一次查找数据的时候,通过根节点当然可以找到其他节点,但是他怎么知道根节点在哪里呢?

根节点也存在 Data Dictionary 中,所以表空间还是比较重要的。


好了,关于页及页内数据行这个层级的结构描述完了,再回到这张图:

里面出现次数最多单词的就是 Tablespace 表空间。下篇文章,我们就来看看表空间结构。等Innodb的结构学习完了,就可以开始另一个层级的学习,即事务、锁等。再往后,就可以来学习分布式数据系统了。

发布了47 篇原创文章 · 获赞 91 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/zhou307/article/details/104597104