InnoDB为不同目的设计了很多不同类型的页,比如undo日志信息的页,存放ChangBuffer的页,而存放表中数据记录的页我们称之为数据页。
1、数据页结构
| 名称 | 中文名 | 占用空间大小 | 描述 |
|---|---|---|---|
| File Header | 文件头部 | 38字节 | 页的一些通用信息 |
| Page Header | 页面头部 | 56字节 | 数据页专有的一些信息 |
| Infimum + Supremum | 最小记录和最大记录 | 26字节 | 两个虚拟的行记录 |
| User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
| Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
| Page Directory | 页面目录 | 不确定 | 页中的某些记录的相对位置 |
| File Trailer | 文件尾部 | 8字节 | 校验页是否完整 |
2、记录在页中的存储
存储的记录会按照指定的行格式存储到 User Records中,一开始页生成时没有User Records部分,每当插入一条记录时从Free Space部分申请一个记录大小的空间,将其划分到User Records,当Free Space全部被User Records代替后就意味着该页已经写满,如果此时还有新记录插入,就需要去申请新的页了。
重新看下记录头,并详细解释部分项含义
| 名称 | 大小(位) | 描述 |
|---|---|---|
| 预留位1 | 1 | 没有使用 |
| 预留位2 | 1 | 没有使用 |
| delete_flag | 1 | 标识当前记录是否被删除 |
| min_rec_flag | 1 | B+树的每层非叶子节点中的最小记录都会添加该标记 |
| n_owned | 4 | 数据页会将记录分组,组内地址偏移量最大的记录会统计该组的记录数 |
| heap_no | 13 | 表示当前记录在页面堆中的相对位置。 |
| record_type | 3 | 当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录(Inflimum),3表示最大记录(Superemum)。 |
| next_record | 16 | 下一条记录的相对位置 |
delete_flag
delete_flag 值为1表示删除,删除之后的记录任然保存在磁盘中,如果将记录从磁盘移除,需要重新漂亮其他记录,会带来性能消耗问题,所以只需要打上删除标记,被删除的记录会形成一个垃圾链表,记录在这个链表中的空间称之为可重用空间,之后若是有新记录插入就有可能覆盖掉被删除记录占用的空间
heap_no
数据页User Records一条一条数据记录称为堆,记录在堆中的相对位置称之为heap_no,页前面记录的heap_no较小,页后面的heap_no较大。InnoDB在每个页面中加入了两个虚拟记录,一条代表页面的最小记录(Infimum记录),一条代表页面的最大记录(Supremun记录),Infimum记录的heap_no为0,Supremun记录的heap_no为1。
next_record
数据页中的记录是一个按照主键值由小到大顺序串联的单向链表
n_owned
为了方便根据主键查找页中的某条记录,数据页会将记录分组,组内地址偏移量最大的记录会统计该组的记录数放在n_owned字段,分组相关详情参考页目录内容
3、页目录
记录在页中是一个按照主键值由小到大顺序串联的单向链表。为了方便根据主键查找页中的某条记录,创建了页目录的结构:
将所有的正常记录(包括Inflimum和Superemum记录,但不包括已经移到垃圾链表的记录)划分为几个组 ,每个组的最后一条记录的头信息n_owned属性表示该组共有几条记录,将每个组最后一条记录在页面的偏移地址即槽(2字节)单独提取出来,按顺序存放在靠近页尾部的地方,该地方即为页目录。
分组说明
分组规定
对于Infimum记录所在分组只能有一条记录,Supremun记录所在分组记录数在1~8条间,剩下分组的记录数只能在4~8条间。
分组过程
1、初始情况下,一个数据页只有Infimum和Supremun两条记录,它们分属两个分组,页目录也只有两个槽,分别为Infimum和Supremun记录在页面的地址偏移量
2、之后每插入一条记录,都会从页目录中找出对应记录主键值比待插入数据主键值大并且差值最小的槽,然后把槽对应记录的记录头n_owned加1,表示本组内又添加了一条数据。
3、当组内记录数等于8后,再插入一条记录,会将组内记录拆分为两组,其中一个组4条,一个组5条,然后在页目录新增一个槽,记录这个新增分组中id最大记录的地址偏移量。
有了页目录我们使用主键查找某条记录就方便的多,以主键为查询条件对页目录按槽进行二分查找,查找到主键对应槽后,遍历槽对应分组记录。
二分查找过程即计算中间槽位置,对比中间槽记录对应主键和查找主键值,再决定查找最小槽到中间槽区间还是中间槽到最大槽区间,重新计算中间槽,直到定位到一个前一个槽主键值小于查找主键值,后一个槽主键值大于等于查找主键值的分组。
4、Page Header 页面头部
页面头部记录了存储在数据页中记录的一些状态信息。
| 属性名称 | 占用空间大小 | 描述 |
|---|---|---|
| PAGE_N_DIR_SLOTS | 2字节 | 在页目录中的槽数量 |
| PAGE_HEAP_TOP | 2字节 | 还未使用的空间最小地址,也就是说从该地址之后就是Free Space |
| PAGE_N_HEAP | 2字节 | 第1位表示本记录是否为紧凑型的记录,剩余的15位表示本页的堆中记录的数量(包括Infimum和Supremum记录以及标记为“已删除”的记录) |
| PAGE_FREE | 2字节 | 各个已删除的记录通过next_record 组成个单向链表,这个单向链表中的记录所占用的存储空间可以被重新利用,PAGE_FREE 表示该链表头节点对应记录在页面 中的偏移量 |
| PAGE_GARBAGE | 2字节 | 已删除记录占用的字节数 |
| PAGE_LAST_INSERT | 2字节 | 最后插入记录的位置 |
| PAGE_DIRECTION | 2字节 | 记录插入的方向 |
| PAGE_N_DIRECTION | 2字节 | 一个方向连续插入的记录数量 |
| PAGE_N_RECS | 2字节 | 该页中用户记录的数量(不包括Inimum和Supremum 记录以及被删除的记录) |
| 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+树的根页面中定义 |
5、File Header 文件头部
FileHeader通用于各种类型的页,描述了通用于各种页的信息。
FileHeader结构及描述
| 属性名称 | 占用空间大小 | 描述 |
|---|---|---|
| FIL_PAGE_SPACE_OR_CHKSUM | 4字节 | 当MySQL的版本低于4.0.14日时,该属性表 示本页面所在的表空间ID;在之后的版本 中,该属性表示页的校验和(checksum) |
| FIL_PAGE_OFFSET | 4字节 | 页号 |
| FIL_PAGE_PREV | 4字节 | 上一个页的页号 |
| FIL_PAGE_NEXT | 4字节 | 下一个页的页号 |
| FIL_PAGE_LSN | 8字节 | 页面被最后修改时对应的LSN ( Log Sequene Number,日志序列号) 值 |
| FIL_PAGE_TYPE | 2字节 | 该页的类型 |
| FILPAGE_FILE_FLUSH_LSN | 8字节 | 仅在系统表空间的第一个页中定义,代表文件至少被刷新到了对应的LSN值 |
| FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4字节 | 页属于哪个表空间 |
其它类型页
| 类型名称 | 十六进制 | 描述 |
|---|---|---|
| FIL_PAGE_TYPE_ALLOCATED | 0x0000 | 最新分配,还未使用 |
| FILPAGE_UNDO LOG | 0x0002 | undo日志页 |
| FIL_PAGE_INODE | 0x0003 | 存储段的信息 |
| FIL_PAGE_IBUF_FREE LIST | 0x0004 | Change Buffer空闲列表 |
| FIL_PAGE_IBUF_BITMAP | 0x0005 | Change 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 | 溢出页 |
| FIL_PAGE_INDEX | 0x45BF | 索引页,也就是我们所说的数据页 |
6、File Trailer 文件尾部
File Trailer 用来校验页面完整性,避免部分刷盘,File Trailer 由8个字节组成,可以分为两个部分
- 前4个字节代表页的校验和
- 该校验和和File Header的校验和相对应。页面在刷盘之前,先将页面的校验和算出来,因为File Header在页面的前边,所以FileHeader中的校验和会先刷盘(操作系统页面大小为4k,InnoDB页面大小16k,所以需要多次刷盘),File Trailer 中的校验和最后刷盘,如果页面刷盘成功,则页首和页尾的校验和应该是一致的。
- 后4个字节代表页面被最后修改时对应LSN的后4个字节,正常情况下应该与File Header的FIL_PAGE_LSN的后4个字节相同,该部分也是为了验证页面完整性。
