前言
大家好,我是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_CHKSUM | 4 | ⻚的校验和(checksum值) |
*FIL_PAGE_OFFSET | 4 | ⻚号 |
*FIL_PAGE_PREV | 4 | 上⼀个⻚的⻚号 |
*FIL_PAGE_NEXT | 4 | 下⼀个⻚的⻚号。通过该属性与FIL_PAGE_PREV属性,实现了B+树中,叶子结点是由双向链表构成,能快速遍历的特性。 |
FIL_PAGE_LSN | 8 | ⻚⾯被最后修改时对应的⽇志序列位置 |
*FIL_PAGE_TYPE | 2 | 该⻚的类型。具体页类型在下表中展示 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 仅在系统表空间的⼀个⻚中定义,代表⽂件⾄少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | ⻚属于哪个表空间 |
页类型,*为重点掌握的页类型。
类型名称 | 十六进制 | 描述 |
---|---|---|
FIL_PAGE_TYPE_ALLOCATED | 0x0000 | 最新分配,还没使⽤ |
*FIL_PAGE_UNDO_LOG | 0x0002 | Undo⽇志⻚ |
*FIL_PAGE_INODE | 0x0003 | 段信息节点 |
*FIL_PAGE_IBUF_FREE_LIST | 0x0004 | Insert Buffer 空闲列表 |
FIL_PAGE_IBUF_BITMAP | 0x0005 | Insert Buffer 位图 |
*FIL_PAGE_TYPE_SYS | 0x0006 | 系统⻚ |
FIL_PAGE_TYPE_TRX_SYS | 0x0007 | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR | 0x0008 | 表空间头部信息 |
FIL_PAGE_TYPE_XDES | 0x0009 | 扩展描述⻚ |
FIL_PAGE_TYPE_BLOB | 0x000A | BLOB⻚ |
*FIL_PAGE_INDEX | 0x45BF | 索引⻚,也就是我们所说的数据⻚ |
Page Header
上文列举除了很多种类型的页。其中数据页的属性如下表所示。
状态名称 | 大小(字节) | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS | 2 | 在⻚⽬录中的槽数量,在Page Directory中会讲到 |
PAGE_HEAP_TOP | 2 | 还未使⽤的空间最⼩地址,也就是说从该地址之后就是Free Space |
PAGE_N_HEAP | 2 | 本⻚中的记录的数量(包括最⼩和最⼤记录以及标记为删除的记录) |
PAGE_FREE | 2 | 第⼀个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成⼀个单链表,这个单链表中的记录可以被重新利⽤) |
PAGE_GARBAGE | 2 | 已删除记录占⽤的字节数 |
PAGE_LAST_INSERT | 2 | 最后插⼊记录的位置 |
PAGE_DIRECTION | 2 | 记录插⼊的⽅向 |
PAGE_N_DIRECTION | 2 | ⼀个⽅向连续插⼊的记录数量 |
PAGE_N_RECS | 2 | 该⻚中记录的数量(不包括最⼩和最⼤记录以及被标记为删除的记录) |
PAGE_MAX_TRX_ID | 8 | 修改当前⻚的最⼤事务ID,该值仅在⼆级索引中定义 |
PAGE_LEVEL | 2 | 当前⻚在B+树中所处的层级 |
PAGE_INDEX_ID | 8 | 索引ID,表示当前⻚属于哪个索引 |
PAGE_BTR_SEG_LEAF | 10 | B+树叶⼦段的头部信息,仅在B+树的根⻚面定义 |
PAGE_BTR_SEG_TOP | 10 | B+树⾮叶⼦段的头部信息,仅在B+树的根⻚面定义 |
Infimum Record和Supremum Record
每个数据页都有两个虚拟的行记录,用来限定记录的边界。Infimum比该页的任何主键值都小。Supremum比该页的任何主键值都大。如下图所示。
User Record和Free Space
User Record是实际存储行记录的内容。
Free Space是空闲空间,是个链表数据结构。当一条记录被删除后,该空间会被加入到空闲链表中。
Page Directory
数据页的Page Directory用于在页内快速查找某条记录。
分组流程
- 初始情况下⼀个数据⻚⾥只有最⼩记录和最⼤记录两条记录,它们分属于两个分组。
- 最⼩记录所在的分组只能有1条记录,最⼤记录所在的分组拥有的记录条数只能在1-8条,剩下的分组中记录的条数范围只能在是4-8条之间。
- 将每个组的最后⼀条记录的地址偏移量按照从小到大的顺序存储到Page Directory里。Page Directory的这些地址偏移量被称为槽。
- 之后每插⼊⼀条记录,都会从⻚⽬录中找到主键值⽐本记录的主键值⼤并且差值最⼩的槽,然后把该槽对应的记录的n_owned值+1,表示本组内⼜添加了⼀条记录,直到该组中的记录数等于8个为止。
- 在⼀个组中的记录数等于8个后再插⼊⼀条记录时,会将组中的记录拆分成两个组,⼀个组中4条记录,另⼀个5条记录。这个过程会更新当前组对应的槽,且另外会新增⼀个槽来记录这个新增分组中最⼤的那条记录的偏移量。
示例
-
初始情况如下图所示,下图中行记录中属性的含义参见“InnoDB与其它存储引擎--InnoDB的行--COMPACT”。该页有2个组。第1组,也就是Infimum Record所在的组只有1条记录。第2组,也就是Supreme Record所在的组有7条记录。
-
插入1条主键值为2的记录:槽1所指的记录的主键值比待插入记录大,且差值最小。将该组的主键最大记录的n_owned +1。也就是将Supreme Record记录的n_owned +1。然后调整next record指针,调整heap no。结果如下图所示。
-
插入1条主键值为3的记录。槽1所指的记录的主键值比待插入记录大,且差值最小。此时,槽1对应的组已经有8条记录了。则将该组拆分为2组。更新这2组的heap_no,next_record,槽等信息。进一步简化后的示意图如下所示。
⻚中查找指定主键值的记录流程
- 通过⼆分法比较槽所指记录的主键值与待查键值的大小,来确定待查记录所在的槽。
- 确定槽后,从槽所指的记录开始,通过记录的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-表空间。
感谢各位人才的点赞、收藏和评论,干货文章持续更新中,下篇文章再见!