头图

前言

大家好,我是xicheng。最近天气变凉,注意身体。现在继续更新MySQL的InnoDB的相关文章,InnoDB的知识脑图如下所示,大家坐稳了。

InnoDB页简介

默认是16KB。大小只能在第一次初始化MySQL数据目录时指定。是InnoDB用于存放数据与索引的页。

InnoDB数据页大体结构

名称中文名大小(字节)简单描述
File Header文件头38页的一些通用信息
Page Header页头56数据页专有的一些信息
Infimum + SupreMum最小记录和最大记录26两个虚拟的行记录
User Records用户记录不确定实际存储的行记录内容
Free Space空闲空间不确定页中尚未使用的空间
Page Directory页目录不确定页中某些记录的相对位置
File Trailer文件尾部8检验页是否完整

如下图所示

File Header

用来记录页的一些头信息。针对各种类型的页都通用File Header属性如下表所示。*为重点掌握的属性。

名称占用空间(字节)描述
*FIL_PAGE_SPACE_OR_CHKSUM4⻚的校验和(checksum值)
*FIL_PAGE_OFFSET4⻚号
*FIL_PAGE_PREV4上⼀个⻚的⻚号
*FIL_PAGE_NEXT4下⼀个⻚的⻚号。通过该属性与FIL_PAGE_PREV属性,实现了B+树中,叶子结点是由双向链表构成,能快速遍历的特性。
FIL_PAGE_LSN8⻚⾯被最后修改时对应的⽇志序列位置
*FIL_PAGE_TYPE2该⻚的类型。具体页类型在下表中展示
FIL_PAGE_FILE_FLUSH_LSN8仅在系统表空间的⼀个⻚中定义,代表⽂件⾄少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4⻚属于哪个表空间

页类型,*为重点掌握的页类型。

类型名称十六进制描述
FIL_PAGE_TYPE_ALLOCATED0x0000最新分配,还没使⽤
*FIL_PAGE_UNDO_LOG0x0002Undo⽇志⻚
*FIL_PAGE_INODE0x0003段信息节点
*FIL_PAGE_IBUF_FREE_LIST0x0004Insert Buffer 空闲列表
FIL_PAGE_IBUF_BITMAP0x0005Insert Buffer 位图
*FIL_PAGE_TYPE_SYS0x0006系统⻚
FIL_PAGE_TYPE_TRX_SYS0x0007事务系统数据
FIL_PAGE_TYPE_FSP_HDR0x0008表空间头部信息
FIL_PAGE_TYPE_XDES0x0009扩展描述⻚
FIL_PAGE_TYPE_BLOB0x000ABLOB⻚
*FIL_PAGE_INDEX0x45BF索引⻚,也就是我们所说的数据⻚

Page Header

上文列举出了很多种类型的页。其中数据页的属性如下表所示。

状态名称大小(字节)描述
PAGE_N_DIR_SLOTS2在⻚⽬录中的槽数量,在Page Directory中会讲到
PAGE_HEAP_TOP2还未使⽤的空间最⼩地址,也就是说从该地址之后就是Free Space
PAGE_N_HEAP2本⻚中的记录的数量(包括最⼩和最⼤记录以及标记为删除的记录)
PAGE_FREE2第⼀个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成⼀个单链表,这个单链表中的记录可以被重新利⽤)
PAGE_GARBAGE2已删除记录占⽤的字节数
PAGE_LAST_INSERT2最后插⼊记录的位置
PAGE_DIRECTION2记录插⼊的⽅向
PAGE_N_DIRECTION2⼀个⽅向连续插⼊的记录数量
PAGE_N_RECS2该⻚中记录的数量(不包括最⼩和最⼤记录以及被标记为删除的记录)
PAGE_MAX_TRX_ID8修改当前⻚的最⼤事务ID,该值仅在⼆级索引中定义
PAGE_LEVEL2当前⻚在B+树中所处的层级
PAGE_INDEX_ID8索引ID,表示当前⻚属于哪个索引
PAGE_BTR_SEG_LEAF10B+树叶⼦段的头部信息,仅在B+树的根⻚面定义
PAGE_BTR_SEG_TOP10B+树⾮叶⼦段的头部信息,仅在B+树的根⻚面定义

Infimum Record和Supremum Record

每个数据页都有两个虚拟的行记录,用来限定记录的边界。Infimum比该页的任何主键值都小。Supremum比该页的任何主键值都大。如下图所示。

User Record和Free Space

User Record是实际存储行记录的内容。
Free Space是空闲空间,是个链表数据结构。当一条记录被删除后,该空间会被加入到空闲链表中。

Page Directory

数据页的Page Directory用于在页内快速查找某条记录。
分组流程

  1. 初始情况下⼀个数据⻚⾥只有最⼩记录和最⼤记录两条记录,它们分属于两个分组。
  2. 最⼩记录所在的分组只能有1条记录,最⼤记录所在的分组拥有的记录条数只能在1-8条,剩下的分组中记录的条数范围只能在是4-8条之间。
  3. 将每个组的最后⼀条记录的地址偏移量按照从小到大的顺序存储到Page Directory里。Page Directory的这些地址偏移量被称为槽。
  4. 之后每插⼊⼀条记录,都会从⻚⽬录中找到主键值⽐本记录的主键值⼤并且差值最⼩的槽,然后把该槽对应的记录的n_owned值+1,表示本组内⼜添加了⼀条记录,直到该组中的记录数等于8个为止。
  5. 在⼀个组中的记录数等于8个后再插⼊⼀条记录时,会将组中的记录拆分成两个组,⼀个组中4条记录,另⼀个5条记录。这个过程会更新当前组对应的槽,且另外会新增⼀个槽来记录这个新增分组中最⼤的那条记录的偏移量。

示例

  1. 初始情况如下图所示,下图中行记录中属性的含义参见“InnoDB与其它存储引擎--InnoDB的行--COMPACT”。该页有2个组。第1组,也就是Infimum Record所在的组只有1条记录。第2组,也就是Supreme Record所在的组有7条记录。
  2. 插入1条主键值为2的记录:槽1所指的记录的主键值比待插入记录大,且差值最小。将该组的主键最大记录的n_owned +1。也就是将Supreme Record记录的n_owned +1。然后调整next record指针,调整heap no。结果如下图所示。
  3. 插入1条主键值为3的记录。槽1所指的记录的主键值比待插入记录大,且差值最小。此时,槽1对应的组已经有8条记录了。则将该组拆分为2组。更新这2组的heap_no,next_record,槽等信息。进一步简化后的示意图如下所示。

⻚中查找指定主键值的记录流程

  1. 通过⼆分法比较槽所指记录的主键值与待查键值的大小,来确定待查记录所在的槽。
  2. 确定槽后,从槽所指的记录开始,通过记录的next_record属性遍历该槽所在的组中的各个记录,至到找到指定主键的记录,或者遍历完整个组为止。

File Trailer

所有页类型的File Trailer都相同,一共有8个字节,分成2个部分。

前4个字节代表⻚的校验和(checksum),这个部分和FileHeader中FIL_PAGE_SPACE_OR_CHKSUM相对应的。具体通过InnoDB的checksum函数来运算两者,将运算结果进行比较。如果结果相同,才代表页面被完整的刷新到了磁盘。(因为刷盘是先刷File Header,后刷File Trailer)

后4个字节与File Header中的FIL_PAGE_LSN相同,这个部分也是为了校验⻚的完整性的。

结尾

MySQL数据页就讲完了,希望大家能持续关注下去。下一篇MySQL文章讲InnoDB-表空间。

微信扫描下方二维码,或搜索“xicheng”,关注公众号后回复【笔记】,有我准备的15万字Java面试笔记。
图片
  感谢各位人才的点赞、收藏和评论,干货文章持续更新中,下篇文章再见!


西城
1 声望0 粉丝

一个互联网从业者,一起聊工作,聊生活。