数据页结构的快速浏览
页的本质就是一块16KB大小的存储空间,InnoDB为了不同的目的而把页分为不同的类型,比如存放表空间头部信息的页,存放 Insert Buffer信息的页,存放 INODE 信息的页,存放 undo 日志信息的页等等。数据页代表的这块16KB大小的存储空间可以被划分为多个部分,不同部分有不同的功能,各个部分如图所示:
| 名称 | 中文名 | 占用空间大小 | 简单描述 |
|---|---|---|---|
File Header | 文件头 | 38字节 | 一些描述页的信息 |
Page Header | 页头 | 56字节 | 页的状态信息 |
Infimum + Supremum | 最小记录和最大记录 | 26字节 | 两个虚拟的行记录 |
User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory | 页目录 | 不确定 | 页中的记录相对位置 |
File Trailer | 文件结尾 | 8字节 | 校验页是否完整 |
记录在页中的存储
在页的7个组成部分中,我们自己存储的记录会按照我们指定的行格式存储到User Records部分。是在一开始生成页的时候,其实并没有User Records这个部分,每当我们插入一条记录,都会从Free Space部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了,这个过程的图示如下:
不管我们向页中插入了多少自己的记录,InnoDB都规定他们定义的两条伪记录分别为最小记录与最大记录。这两条记录的构造十分简单,都是由5字节大小的记录头信息和8字节大小的一个固定的部分组成的,如图所示
Page Directory
Page Directory 即页目录,相当于对User Record的真实行记录创建的一个稀疏目录。当User Record中的记录比较多时,查询数据将会耗时增多(尤其是在行记录之间是以链表的形式来存储的)。因此,有了Page Dircetory,我们把User Record分为n个段,并将每一段的第一个行记录的RowID的第一个字节的**相对地址(相对于页的起始地址)**用2个字节存放在Page Directory中,称这个相对地址(指针)为槽(Slots),那么分为n个段,则Page Directory中将存放n个槽。如此,可以先在Page Directory中用二分法缩小查询范围,再在User Record中顺序查找相应记录,缩短了查询时间。
注意行格式中的头信息中的n_owned属性
Page Header
Page Header描述的是页内的各种状态信息,比方说页里头有多少个记录了呀,有多少个槽等。
| 名称 | 占用空间大小 | 描述 |
|---|---|---|
PAGE_N_DIR_SLOTS | 2字节 | 在页目录中的槽数量 |
PAGE_HEAP_TOP | 2字节 | 第一个记录的地址 |
PAGE_N_HEAP | 2字节 | 本页中的记录的数量(包括最小和最大记录以及标记为删除的记录) |
PAGE_FREE | 2字节 | 指向可重用空间的地址(就是标记为删除的记录地址) |
PAGE_GARBAGE | 2字节 | 已删除的字节数,行记录结构中delete_flag为1的记录大小总数 |
PAGE_LAST_INSERT | 2字节 | 最后插入记录的位置 |
PAGE_DIRECTION | 2字节 | 最后插入的方向 |
PAGE_N_DIRECTION | 2字节 | 一个方向连续插入的记录数量 |
PAGE_N_RECS | 2字节 | 该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录) |
PAGE_MAX_TRX_ID | 8字节 | 修改当前页的最大事务ID,该值仅在二级索引中定义 |
PAGE_LEVEL | 2字节 | 当前页在索引树中的位置,高度 |
PAGE_INDEX_ID | 8字节 | 索引ID,表示当前页属于哪个索引 |
PAGE_BTR | 10字节 | 非叶节点所在段的segment header,仅在B+树的Root页定义 |
PAGE_LEVEL | 10字节 | B+树所在段的segment header,仅在B+树的Root页定义 |
File Header
File Header描述的就是页外的各种状态信息,比方说这个页的编号是多少,它的上一个页、下一个页等。File Header是InnoDB页的第一部分,这个部分占用固定的38个字节。
| 名称 | 占用空间大小 | 描述 |
|---|---|---|
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值,独立表空间中都是0 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4字节 | 页属于哪个表空间 |
FIL_PAGE_TYPE
| 名称 | 十六进制 | 描述 |
|---|---|---|
FIL_PAGE_ALLOCATED | 0x0000 | 最新分配,还没使用 |
FIL_PAGE_UNDO_LOG | 0x0002 | Undo Log页 |
FIL_PAGE_INODE | 0x0003 | 段信息的节点 |
FIL_PAGE_IBUUF_FRE_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 | File Space Header |
FIL_PAGE_TYPE_XDES | 0x0009 | 扩展描述页 |
FIL_PAGE_TYPE_BLOB | 0x000A | BLOB页 |
FIL_PAGE_INDEX | 0x45BF | B+树的子节点 |
File Trailer
每页最后8字节为File Trailer部分,这部分是为了保证页的完整性。 前四个字节为checksum校验和,需要通过crc32(默认)算法与File Header中的FIL_PAGE_SPACE_OR_CHKSUM比较。 后四个字节要与File Header中的FIL_PAGE_LSN值比较。
备注
MySQL 是怎样运行的第5章