前言
我们大多数时候,在操作数据库一般只关注如何保存或者获取到正确的数据,但是对于数据是以何种格式存储到磁盘里少有去了解。个人觉得理解这个过程有很大意义,于是顺带好奇对MySQL数据库InnoDB存储引擎中Page页进行探究。
一、Page是什么?
Page是InnoDB存储引擎磁盘管理最小单位,默认大小为16k。我们也可以将通过参数设置为4k、8k、16k。有个问题需要注意,是不能设置为5k或者6k,因操作系统页大小就是4k。Page还分类,在缓冲池里常见的类型有:索引页、数据页、Undo页、系统页等
以下是page的结构图,可见page是由7个部分组成
File Header |
Page Header |
Infimum 和Supremum Records |
User Records |
Free Space |
Page Directory |
File Trailer |
File Header
file header的作用是用来记录页的一些通用信息,站用空间38个字节。
以下表格展示是file header组成部分
名称 | 大小(字节) | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4 | 存储页的checksum值,用于页的校验,校验页是否完整和损坏 |
FIL_PAGE_OFFSET | 4 | 表空间中页的偏移值。如某独立表空间a.ibd的大小为1GB,如果页的大小为16k,那么总共有65536个页。FIL_PAGE_OFFSET表示该页在所有页中的位置,如果此表空间id为10,那么搜索页(10,1)就表示查找表a中的第二页 |
FIL_PAGE_PREV | 4 | 当前页的上一个页,B+Tree的特性决定了叶子结点是双向链表 |
FIL_PAGE_NEXT | 4 | 当前页的下一个页,B+Tree的特性决定了叶子结点是双向链表 |
FIL_PAGE_LSN | 8 | 该值代表该页最后被修改的日志序列位置LSN,当系统宕机了在重启恢复应用日志阶段,如果redo log的lsn小于等于该值,就不需要重做redo log日志了 |
FIL_PAGE_TYPE | 2 | 记录该页属于什么类型如索引页、数据页、Undo页、系统页等 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 当InnoDB正常关闭,在刷新redo log和脏页后,会做一次完全同步的checkpoint,并将checkpoint的LSN写到表空间的FSP HEADER PAGE的FIL_PAGE_FILE_FLUSH_LSN变量中,启动后会拿该变量进行比较,判断是否正常关闭,是否需要故障恢复 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 该值表示页属于哪个表空间 |
Page Header
page header表示页面的头部,用来记录数据页本身的专有信息,站用56个字节。
以下表格展示是 Page Header组成部分
名称 | 大小(字节) | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS | 2 | 在Page Directory中Slot的数量,初始值为2 |
PAGE_HEAP_TOP | 2 | 堆中第一个记录指针 |
PAGE_N_HEAP | 2 | 堆中的记录数,一共占用2字节,但是第15位表示行记录格式 |
PAGE_FREE | 2 | 指向可重用空间的首指针 |
PAGE_GARBAGE | 2 | 已标记为删除记录的字节数 |
PAGE_LAST_INSET | 2 | 最后插入记录的位置 |
PAGE_DIRECTION | 2 | 最后插入方向 |
PAGE_N_DIRECTION | 2 | 一个方向连续插入记录的数量 |
PAGE_N_RECS | 2 | 该页中记录的数量 |
PAGE_MAX_TRX_ID | 8 | 修改该页最大事务ID |
PAGE_LEVEL | 2 | 当前页在索引树中的位置,0x00代表叶子节点。 |
PAGE_INDEX_ID | 8 | 索引页的索引id |
Infimum and Supremum Records
主要是用来限定User Records的边界,Infimum 记录的是比该页任何主键值都小的值,Supremum记录是比该页任何主键值都大的值,这两个值在页创建是被建立,并且在任何情况下不会被删除。
User Records
实际存储用户插入的行内容,每一行记录都有指向下一行的指针,是个单向链表结构。
Free Space
用来管理空闲空间,也是一个链表结构。如一条记录被删除后,该空间会被加入到空闲列表中。
Page Directory
page directory是页目录,作用当数据页加载到内存时,通过page directory进行二叉查找,快速找到行记录。
File Trailer
file trailer用来检测页是否完整。总共8个字节,前4个字节存储变量innodb_checksums,每次从磁盘读取一个页就是通过该变量来检测页的完整性。后4个字节存储变量innodb_checksum_algorithm用来控制检测checksum函数算法。
二、行记录格式
前面主要讲了page一些结构组成部分,但实际上用户插入记录的时候,存在page里是以行记录存储。在InnoDB里行记录格式有:compact、redundant、compressed、dynamic。
compact行记录格式
compact行记录格式结构如下图
变长字段长度列表 | NULL标志位 | 记录头信息 | 列1数据 | 列2数据 | … | DB_ROW_ID | DB_TRX_ID | DB_ROLL_PTR |
---|
变长字段长度列表
变长字段长度列表是用来存储一行记录非null变长字段长度的列表,比如设置字段属性varchar(50),但实际存储数据长度有可能少于50,此时需要变长字段长度列表记录实际存储数据长度。
关于变长字段长度列表有以下两个规则:
- 若列的长度少于255字节,用1字节表示。
- 若列的长度大于255字节,用2个字节表示。
NULL标志位
null标志位主要记录列是否为null值,是用1表示,站用1个字节。
记录头信息
compact行格式记录头信息,站用5个字节,组成部分如下表格:
名称 | 大小(字节) | 描述 |
---|---|---|
() | 1 | 未知 |
() | 1 | 未知 |
deleted_flag | 1 | 该行是否已被删除 |
min_rec_flag | 1 | 为1,表示该行是预先定义最小的记录行 |
n_owned | 4 | 该记录拥有的记录数 |
head_no | 13 | 索引堆中,该条记录的排序记录 |
record_type | 3 | 记录类型:000表示普通,001表示B+树节点指针,010表示infimum,011表示supremum,1xx表示保留 |
next_record | 16 | 页中下一条记录的相对未知 |
total | 40 | 该页中记录的数量 |
列数据
实际存储的数据列
隐藏列
隐藏列用户是查不到的,当InnoDB发现用户没有自定义主键或者没有合适数据列作为主键,此时会自己创建主键,DB_ROW_ID字段就是用来存放自主创建的主键。DB_TRX_ID表示事物ID,DB_ROLL_PTR表示回滚指针,它们的作用是用于事务回滚
行溢出
InnoDB的页大小为16k,总共16384个字节。那实际可以存储记录长度为多少时,会发生溢出?官方手册定义可设置varchar最大长度为65535,其实可以理解为所有varchar列总和为65535。这样不就超过页的大小?首先16384字节减去其他额外字段长度如回滚指针,事务ID等,实际存储长度还要少于16384。当存储记录长度为65535时,其实只保存varchar(65535)的前768字节的前缀数据,剩余的会存储在BlOB页。
redundant行记录格式
redundant是MySQL5.0之前老版本格式,MySQL5.0之后版本支持redundant格式是为了兼容老版本,本文不进行讨论。
compressed行记录格式
compressed行记录格式,存储的行数据会以zlib的算法进行压缩,适用于BLOB、TEXT、VARCHAR这种大长度类型字段。
dynamic行记录格式
dynamic行记录格式是现在mysql默认行记录格式,和compact基本一样,唯一区别是当发生行溢出时,数据页中只存放指向BLOB页的20个字节指针,BLOB页实际存储数据。
总结
本文简单介绍了MySQL数据库InnoDB存储引擎中Page页的内部结构和行记录格式。现在新版本默认行记录格式都是dynamic,和compact区别主要在于行溢出处理方式不同。此外我们也知道compressed是对行数据进行压缩的,对存放特别大的字段可以节省存储空间。
参考资料
MySQL技术内幕InnoDB存储引擎第2版