数据页
InnoDB数据页默认为16KB,该大小只能在初始化数据目录时修改,一旦初始化后,将无法再更改。
行格式
COMPACT
| 变长字段长度列表 | NULL值列表 | 记录头信息 | 隐藏列 | 列1的值 | ··· |
|---|---|---|---|---|---|
- 变长字段长度列表:当表中存在变长字段,如varchar,该字段存储变长字段的实际长度,并按照列顺序的逆序存放(列n ··· 列1的长度)。
- NULL值列表:当表中有允许NULL的列时存在该字段,使用二进制方式按列逆序记录。1为是NULL。
- 记录头信息:固定5字节,40位来描述记录的一些属性。
特殊的,如果使用定长字段如char,但使用的字符集是变长编码的,如utf8,则该字段长度也会存储在变长字段长度列表中。
一个数据页16KB,如果行数据超过了这个大小,会使用溢出页存储,即该行的最后20个字节记录下一页的地址,下一页存储剩余的数据。
DYNAMIC
5.7默认行格式,大概结构与compact差不多,但是将行的所有真实数据都存在溢出页中,行只存溢出页的地址。
隐藏列
mysql为每行记录添加的列。
| 列名 | 必须 | 空间/字节 | 描述 |
|---|---|---|---|
| DB_ROW_ID | 否 | 6 | 行id,唯一标识一行记录 |
| DB_TRX_ID | 是 | 6 | 事务ID |
| DB_ROLL_PTR | 是 | 7 | 回滚指针 |
DB_ROW_ID仅表没有指定主键,且没用非空唯一的字段时才会创建。
数据页结构
数据页结构
| 名称 | 中文名称 | 空间大小(字节) | 描述 |
|---|---|---|---|
| File Header | 文件头部 | 38 | 页通用信息 |
| Page Header | 页头部 | 56 | 数据页专有信息 |
| Infimum+Supremum | 页中最小和最大记录 | 26 | 两个虚拟记录 |
| User Records | 用户记录 | 不确定 | 用户存储的记录 |
| Free Space | 空闲空间 | 不确定 | 页中未使用的空间 |
| Page Directory | 页目录 | 不确定 | 页中某些记录相对位置 |
| File Trailer | 文件尾部 | 8 | 校验页的完整性 |
每次插入记录时,会从空闲空间申请一个记录大小的空间,划分到用户记录,当空闲空间全被使用,代表页用完了。
数据行头信息
| 名称 | 大小(比特) | 描述 |
|---|---|---|
| 预留位1 | 1 | 未使用 |
| 预留位2 | 1 | 未使用 |
| deleted_flag | 1 | 删除标记 |
| min_rec_flag | 1 | B+树中每层非叶子节点最小的目录项记录会添加该标志 |
| n_owned | 4 | 页面记录分为组,组长记录的该值是组员数量,组员记录该值为0 |
| heap_no | 13 | 当前记录在页面堆中的相对位置 |
| record_type | 3 | 记录类型;0-普通;1-B+树非叶子目录;2-最小;3-最大 |
| next_record | 16 | 下一条记录相对位置 |
- deleted_flag:当一条记录被标记删除时,该记录的空间可被重用,且放入垃圾链表。不直接删除是为了减少删除后的整理消耗。
- min_rec_flag:叶子节点的该值均为0。
- n_owned:该组的组员数量,详细见下文页目录
- heap_no:用户记录紧凑的排列在一起,构成记录堆。后面的记录比前面的记录该值大1,且每页有自动添加的Infimum和Supremum隐藏记录,分表代表存储页中的最小、最大记录(根据主键比较),其该值分别为0和1。注意,Infimum和Supremum只是相当于链表的一个空的Head和Tail,并不存储真正极值记录的信息。
- next_record:下一条记录(主键大小的下一个)的地址,即B+叶子节点中的链表组成。同时页中Infimum的该值指向该页主键最小的记录,页中主键最大的记录的该值指向Supremum记录。在删除记录时,该值也会同步更新
页目录
在页中查询某个记录时,从最小记录遍历查找,时间复杂度O(n),为了降低复杂度,使用分组概念。
页中记录,分为多个组,每个组的最后一个记录为组长,其头信息中的n_owned储存该组的组员数量。
在查找时,先根据组进行二分查找确定在哪个组,再到具体的组内进行遍历查找,有点跳表的思想。
在页靠近尾部的地方有着页目录空间,提取组长的地址偏移量作为槽,,即每个槽都指向每个组的组长。
默认有两个槽,Infimum和Supremum。前者n_owned固定为1,后者不定。
每个组的成员数量在4-8之间,如果超过8,会新增一个槽,即新建一个分组进行对半分。
页头部
用于存储各种状态信息。
| 状态名称 | 占用空间大小 | 描述 |
|---|---|---|
| PAGE_N_DIR_SLOTS | 2 | 页目录中槽的数量 |
| PAGE_HEAP_TOP | 2 | 还没使用的空间最小地址 |
| PAGE_N_HEAP | 2 | 第一位标识是否为紧凑型记录;剩余15位标识本页堆中记录数量,包括标记为已删除的记录 |
| PAGE_FREE | 2 | 已删除记录链表头节点记录在页面的偏移量 |
| 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_INDX_ID | 8 | 索引ID,表示当前页属于哪个索引 |
| PAGE_BTR_SEG_LEAF | 10 | B+树叶子节点段的头部信息,仅在B+树根页面中定义 |
| PAGE_BTR_SEG_TOP | 10 | B+树非叶子节点段的头部信息,仅在B+树根页面中定义 |
文件头部
固定占用38字节,数据页的第一个组成部分,用于描述页的部分信息。
| 状态名称 | 占用空间大小(字节) | 描述 |
|---|---|---|
| FIL_PAGE_SPACE_OR_CHKSUM | 4 | 页的校验和 |
| FIL_PAGE_OFFSET | 4 | 页号 |
| FIL_PAGE_PREV | 4 | 上一页页号 |
| FIL_PAGE_NEXT | 4 | 下一页页号 |
| FIL_PAGE_LSN | 8 | 页面最后修改时对应的日志序列号 |
| FIL_PAGE_TYPE | 2 | 页类型 |
| FIL_PAGE_FILE_FLUSH_LSN | 8 | 仅在系统表空间的第一个页中定义,代表文件至少被刷新到了对应的LSN值 |
| FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 页属于哪个表空间 |
文件尾部
8个字节,为了防止数据页刷盘时意外断电的完整性校验。
前四个字节位数据页校验和,与文件头中的FIL_PAGE_SPACE_OR_CHKSUM一致,在刷盘时,头部的FIL_PAGE_SPACE_OR_CHKSUM首先被刷到磁盘,如果正常刷完,尾部的校验和也会一并刷进去。如果刷一半,会造成头部和尾部的校验和不一致,代表刷盘过程出现意外。
后四个字节对应最后修改时对应的LSN的后四个字节,与文件头的FIL_PAGE_FILE_FLUSH_LSN后四个字节一致。同样用于校验页的完整性。
