页
是InnoDB
管理存储空间的基本单位,一个页的大小一般是16KB
。
InnoDB
有许多种不同类型的页
,如存放表空间头部信息的页,存放Insert Buffer
信息的页,存放INODE
信息的页,存放undo
日志信息的页等等,存放记录的页被称为索引(INDEX
)页
索引页结构:
名称 | 中文名 | 占用空间大小 | 简单描述 |
File Header | 文件头部 | 38 字节 | 页的一些通用信息 |
Page Header | 页面头部 | 56 字节 | 数据页专有的一些信息 |
Infimum + Supremum | 最小记录和最大记录 | 26 字节 | 两个虚拟的行记录 |
User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页面目录 | 不确定 | 页中的某些记录的相对位置 |
File Trailer | 文件尾部 | 8 字节 | 校验页是否完整 |
Page Directory(页目录)
已知记录在页中按照主键值由小到大顺序串联成一个单链表,InnoDB
将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组,每个组的最后一条记录(即组内最大的那条记录)的头信息中的n_owned
属性表示该组内共有几条记录,然后将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到Page Directory
,也就是页目录
。页面目录中的这些地址偏移量被称为槽
(英文名:Slot
),页面目录就是由槽
组成的。
分组规则:
最小记录所在的分组只能有 1 条记录
最大记录所在的分组拥有的记录条数为 1~8 条
其他的分组中记录的条数范围为 4~8 条
示例如图:
Page Header(页面头部)
存储数据页中各种状态信息,如存储了多少条记录,第一条记录的地址,页目录中存储了多少个槽等等,占用固定的56
个字节
内容如下:
名称 | 占用空间大小 | 描述 |
PAGE_N_DIR_SLOTS | 2 字节 | 在页目录中的槽数量 |
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+树的Root页定义 |
PAGE_BTR_SEG_TOP | 10 字节 | B+树非叶子段的头部信息,仅在B+树的Root页定义 |
PAGE_DIRECTION
假如新插入的一条记录的主键值比上一条插入的记录的主键值大,我们说这条记录的插入方向是右边,反之则是左边
PAGE_N_DIRECTION
假设连续几次插入新记录的方向都是一致的,
InnoDB
会把沿着同一个方向插入记录的条数记下来,这个条数就用PAGE_N_DIRECTION
这个状态表示。如果最后一条记录的插入方向改变了的话,这个状态的值会被清零重新统计。
File Header(文件头部)
记录一些针对各种页都通用的信息,如这个页号,上一个页、下一个页的页号等等,占用固定的38
个字节,不同类型的页都会以File Header
作为第一个组成部分
内容如下:
名称 | 占用空间大小 | 描述 |
FIL_PAGE_SPACE_OR_CHKSUM | 4 字节 | 页的校验和(checksum值) |
FIL_PAGE_OFFSET | 4 字节 | 页号 |
FIL_PAGE_PREV | 4 字节 | 上一个页的页号 |
FIL_PAGE_NEXT | 4 字节 | 下一个页的页号 |
FIL_PAGE_LSN | 8 字节 | 页面被最后修改时对应的日志序列位置(英文名:Log Sequence Number) |
FIL_PAGE_TYPE | 2 字节 | 页的类型 |
FIL_PAGE_FILE_FLUSH_LSN | 8 字节 | 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 字节 | 页属于哪个表空间 |
FIL_PAGE_SPACE_OR_CHKSUM
代表当前页面的校验和(checksum)。
通过某种算法来计算一个比较短的值来代表这个很长的字节串,这个比较短的值就称为
校验和
。在比较两个很长的字节串之前先比较这两个长字节串的校验和,如果校验和不一样则两个长字节串肯定不同,省去直接比较两个比较长的字节串的时间损耗。
FIL_PAGE_OFFSET
每一个
页
都有一个唯一的页号,InnoDB
通过页号来定位唯一一个页
。
FIL_PAGE_TYPE
InnoDB
为了不同的目的而把页分为不同的类型,上边介绍的都是存储数据记录的数据页
具体如下:
类型名称 十六进制 描述 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 溢出页 FIL_PAGE_INDEX 0x45BF 索引页,即 数据页
FIL_PAGE_PREV
和FIL_PAGE_NEXT
InnoDB
以页为单位存放数据,数据占用的空间非常大(一张表中可以有成千上万条记录)时,数据可能被分散存储到多个不连续的页中,FIL_PAGE_PREV
和FIL_PAGE_NEXT
分别代表本页的上一个和下一个页的页号,这样就建立起一个双向链表把许许多多的页就都串联起来,而无需这些页在物理上真正连着。并不是所有类型的页都有上一个和下一个页的属性
图示如下:
File Trailer
InnoDB
存储引擎把数据存储到磁盘上,但是磁盘速度太慢,需要以页
为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,那么在修改后的某个时间需要把数据同步到磁盘中。如果在同步了一半的时候发生断电等意外,同步未完成,就会导致数据页中的数据不完整。
为了检测一个页是否完整,InnoDB
在每个页的尾部都加了一个File Trailer
部分,这个部分由8
个字节组成,分成2个小部分:
前4个字节代表页的校验和
和
File Header
中的校验和相对应的。当一个页面在内存中被修改了,在同步之前先计算它的校验和,校验和会被首先同步到对应磁盘页中的File Header,当完全写完时,校验和也会被写到磁盘页中的File Trailer,如果完全同步成功,则页的File Header和File Trailer中的校验和应该是一致的。如果写了一半儿断电了,那么在File Header
中的校验和就代表着已经修改过的页,而在File Trailer
中的校验和代表着原先的页,二者不同则意味着同步中间出了错
后4个字节代表页面被最后修改时对应的日志序列位置(LSN)
页面最后修改时对应的
LSN
值,也是为了校验页的完整性的