相信通过前面几个小节的介绍,读者已经知道页是 InnoDB存储引擎管理数据库的最小磁盘单位。页类型为B-Tree Node的页存放的即是表中行的实际数据了。在这一节中,我们将从底层具体地介绍 InnoDB数据页的内部存储结构注意 InnoDB公司本身并没有详细介绍其页结构的实现, MySQL的官方手册中也基本没有提及 InnoDB存储引擎页的内部结构。
InnoDB数据页由以下7个部分组成,如图所示。
- File header(文件头)
- Page Header(页头)
- Infimum和 Supremum Records
- User records(用户记录,即行记录)
- Free Space(空闲空间)
- Page Directory(页目录)
- File Trailer(文件结尾信息)
其中 File header、 Page Header、 File trailer的大小是固定的,分别为38、56、8字节,这些空间用来标记该页的一些信息,如 Checksum,数据页所在B+树索引的层数等。 User Records、 Free Space、 Page Directory这些部分为实际的行记录存储空间,因此大小是动态的。
1、File Header
File header用来记录页的一些头信息,由表4-3中8个部分组成,共占用38字节。
File header组成部分
名称
大小(字节)
说明
FIL_PAGE_SPACE_OR_CHKSUM
4
当 MySQL为 MySQL40.14之前的版本时,该值为0。在之后的 MySQL版本中,该值代表页的 checksum值(一种新的 checksum值)
FIL_PAGE_OFFSET
4
表空间中页的偏移值。如某独立表空间a.ibd的大小为1GB,如果页的FIL_PAGE_OFFSET大小为16KB,那么总共有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( Log Sequence Number)
FIL_PAGE_TYPE
2
InnoDB存储引擎页的类型。常见的类型见下表。记住0x45BF,该值代表了存放的是数据页,即实际行记录的存储空间
LSN_FLUSH_LSN
8
该值仅在系统表空间的一个页中定义,代表文件至少被更新到了该LSN_FLUSH_LSN值。对于独立表空间,该值都为0
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID
4
从 MySQL4.1开始,该值代表页属于哪个表空间
InnoDB存储引擎页的类型
名称
十六进制
解释
FIL_PAGE_INDEX
0x45BF
B+树叶节点
FIL_PAGE_UNDO_LOG
0x0002
Undo Log页
FIL_PAGE_INODE
0x0003
索引节点
FIL_PAGE_IBUF_FREE_LIST
0x0004
Insert Buffer空闲列表
FIL_PAGE_TYPE_ALLOCATED
0x0000
该页为最新分配
FIL_PAGE_IBUF_BITMAP
0x0005
Insert Buffer位图
FIL_PAGE_TYPE_SYS
0x0006
系统页
FIL_PAGE_TYPE_TRX_SYS
0x0007
事务系统数据
FIL_PAGE_TYPE_FSP_HDR
0x0008
File Space Header
FIL_PAGE_TYPE_XDES
0x0009
扩展描述页
FIL_PAGE_TYPE_BLOB
0x000A
BLOB页
2、Page Header
接着 File header部分的是 Page Header,该部分用来记录数据页的状态信息,由14个部分组成,共占用56字节,如下表所示。
Page Header组成部分
名称
大小(字节)
说明
PAGE_N_DIR SLOTS
2
在 Page Directory(页目录)中的Slot(槽)数,“Page Directory”小节中会介绍
PAGE_HEAP_TOP
2
堆中第一个记录的指针,记录在页中是根据堆的形式存放的
PAGE_N_HEAP
2
堆中的记录数。一共占用2字节,但是第15位表示行记录格式
PAGE_FREE
2
指向可重用空间的首指针
PAGE_GARBAGE
2
已删除记录的字节数,即行记录结构中 delete flag为1的记录大小的总数
PAGE_LAST_INSERT
2
最后插入记录的位置
PAGE_DIRECTION
2
最后插入的方向。可能的取值为:
PAGE_LEFT(0x01)
PAGE_RIGHT(0x02)
PAGE_DIRECTION
PAGE_SAME_REO(0x03)
PAGE_SAME_PAGE(0x04)
PAGE_NO_DIRECTION (Ox05)
PAGE_N_DIRECTION
2
一个方向连续插人记录的数量
PAGE_NRECS
2
该页中记录的数量
PAGE_MAX_TRX_ID
8
修改当前页的最大事务ID,注意该值仅在 Secondary Index中定义
PAGE_LEVEL
2
当前页在索引树中的位置,0x00代表叶节点,即叶节点总是在第0层
PAGE_INDEX_ID
8
索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF
10
B+树数据页非叶节点所在段的 segment header。注意该值仅在B+树的Root页中定义
PAGE_BTR_SEG_TOP
10
B+树数据页所在段的 segment header。注意该值仅在B+树的Root页中定义
3、Infimum和Supremum Record
在 InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用来限定记录的边界。 Infimum记录是比该页中任何主键值都要小的值, Supremum指比任何可能大的值还要大的值。这两个值在页创建时被建立,并且在任何情况下不会被删除。在 Compact行格式和 Redundant行格式下,两者占用的字节数各不相同。下图显示了 Infimum和Supremum记录。
4、User Record 和 Free Space
User Record就是之前讨论过的部分,即实际存储行记录的内容。再次强调, InnoDB存储引擎表总是B+树索引组织的。
Free Space很明显指的就是空闲空间,同样也是个链表数据结构。在一条记录被删除后,该空间会被加入到空闲链表中。
5、Page Directory
Page Directory(页目录)中存放了记录的相对位置(注意,这里存放的是页相对位置,而不是偏移量),有些时候这些记录指针称为Slots(槽)或目录槽( Directory Slots)。与其他数据库系统不同的是,在 InnoDB中并不是每个记录拥有一个槽, InnoDB存储引擎的槽是一个稀疏目录( sparse directory),即一个槽中可能包含多个记录。伪记录 Infimum的 n_owned值总是为1,记录 Supremum的n_owned的取值范围为[1,8],其他用户记录 n_owned的取值范围为[4,8]。当记录被插入或删除时需要对槽进行分裂或平衡的维护操作。
在 Slots中记录按照索引键值顺序存放,这样可以利用二叉查找迅速找到记录的指针。假设有(i,'d,'c',"b','e',"g"',"l',"h','f",∵j',"k',"a')同时假设一个槽中包含4条记录,则 Slots中的记录可能是('a','e','i')。
由于在 InnoDB存储引擎中 Page Direcotry是稀疏目录,二叉查找的结果只是一个粗略的结果,因此 InnoDB存储引擎必须通过recorder header中的 next record来继续查找相关记录。同时, Page Directory很好地解释了 recorder header中的n_ owned值的含义,因为这些记录并不包括在 Page Directory中。
需要牢记的是,B+树索引本身并不能找到具体的一条记录,能找到只是该记录所在的页。数据库把页载入到内存,然后通过Page Directory再进行二叉查找。只不过二叉査找的时间复杂度很低,同时在内存中的查找很快,因此通常忽略这部分查找所用的时间。
6、File Trailer
为了检测页是否已经完整地写入磁盘(如可能发生的写入过程中磁盘损坏、机器关机等), InnoDB存储引擎的页中设置了 File trailer部分。
File trailer只有一个FIL_PAGE_ END_LSN部分,占用8字节。前4字节代表该页的 checksum值,最后4字节和 File header中的 FIL_PAGE_LSN相同。将这两个值与File Header中的 FIL_PAGE_SPACE_OR_CHKSUM和 FIL_PAGE_LSN值进行比较,看是否一致( checksum的比较需要通过 InnoDB的 checksum函数来进行比较,不是简单的等值比较),以此来保证页的完整性( not corrupted)。
在默认配置下, InnoDB存储引擎每次从磁盘读取一个页就会检测该页的完整性,即页是否发生 Corrupt,这就是通过 File trailer部分进行检测,而该部分的检测会有一定的开销。用户可以通过参数 innodb checksums来开启或关闭对这个页完整性的检查MySQL56.6版本开始新增了参数 innodb checksum algorithm,该参数用来控制检测 checksum函数的算法,默认值为crc32,可设置的值有: innodb、crc32、none、 strict innodb、 strict crc32、 strict none。
innodb为兼容之前版本 InnoDB页的 checksum检测方式,crc32为 MySQL5.6.6版本引进的新的 checksum算法,该算法较之前的 innodb有着较高的性能。但是若表中所有页的 checksum值都以 strict算法保存,那么低版本的 MySQL数据库将不能读取这些页。none表示不对页启用 checksum检查strict*正如其名,表示严格地按照设置的 checksum算法进行页的检测。因此若低版本 MySQL数据库升级到 MySQL566或之后的版本,启用 strict crc32将导致不能读取表中的页。启用 strict crc32方式是最快的方式,因为其不再对 innodb和crc32算法进行两次检测。故推荐使用该设置。若数据库从低版本升级而来,则需要进行 mysql_upgrade操作。