简介
innodb把存放用户数据的页称作索引(INDEX)页,为了方便,这里称作数据页。innodb数据页一般为16KB,其结构如下:
File Header
File header 对各种类型的页都是通用的,占用固定的38字节。
- FIL_PAGE_SPACE_OR_CHKSUM:校验和,4字节;
- FIL_PAGE_OFFSET:页号,4字节,每个页都有唯一的页号;
- FIL_PAGE_PREV:上一页的页号,4字节;
- FIL_PAGE_NEXT:下一页的页号,4字节;
- FIL_PAGE_TYPE:页类型,2字节,数据页的类型是FIL_PAGE_INDEX;
- FIL_PAGE_LSN:页最后被修改的LSN(log sequence number),8字节;
- FIL_PAGE_FILE_FLUSH_LSN:8字节,仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值
- FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID:4字节,页所属的表空间;
Page Header
这里介绍的page header是专门针对数据页的,它占56个字节,这里介绍几个重要的字段:
| 名称 | 空间(字节) | 描述 |
|---|---|---|
| PAGE_N_DIR_SLOTS | 2 | Page directory 中slot的数量 |
| PAGE_HEAP_TOP | 2 | 当前free space的起始地址 |
| PAGE_N_HEAP | 2 | 本页记录数量,包含最大最小记录,标记删除的记录 |
| PAGE_N_RECS | 2 | 本页记录数量,不包含最大最小记录和标记删除的记录 |
| PAGE_FREE | 2 | 第一个标记删除的记录地址,被标记删除的记录通过 next-record 串成一个单链表,这个单链表中的记录可以被重新利用 |
| PAGE_GARBAGE | 2 | 标记删除的记录占用的字节数 |
| PAGE_LAST_INSERT | 2 | 最后插入记录的位置 |
| PAGE_MAX_TRX_ID | 8 | 修改该页的最大事务id,仅在二级索引页中定义 |
| PAGE_LEVEL | 2 | 该页在B+树中所处的层级 |
| PAGE_INDEX_ID | 8 | 索引id,代表当前页属于哪个索引 |
User Records + Page Directory
记录在页中按照主键值由小到大顺序串联成一个单链表,当搜索某个记录时要从头遍历吗?当然不是,这样的时间复杂度是O(n),我们有更快的做法,这就是page directory提出的背景。记录以4-8个为一组,page directory中的每个slot指向对应组中最大的一条记录,这样查找时就能应用二分搜索,将复杂度将为O(logn)。
行格式
上图的记录是一种简化的表示,实际上,每一条记录的行格式由建表时的ROW_FORMAT指定,以Compact为例,它包含下面几部份:
这里重点介绍记录头信息:
| 名称 | 空间(bit) | 描述 |
|---|---|---|
| delete_mask | 1 | 标记记录是否删除 |
| min_rec_mask | 1 | B+树每层非叶子节点中的最小记录都会添加该标记 |
| n_owned | 4 | 每组最大的记录,用这个字段保存该组记录数量 |
| heap_no | 13 | 当前记录在本页中的位置,从2开始,0,1是预置的最小最大记录 |
| record_type | 3 | 表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录 |
| next_record | 16 | 下一条记录指针 |
File tailer
File tailer由8字节组成,主要用于页完整性的校验:
- 前4个字节是校验和,每当一个页面在内存中修改了,在同步之前就要把它的校验和算出来。正常情况下它与File header里的校验和相同,那为什么还要引入它呢?这是因为,假如内存中的数据正在刷盘,刷到一半断电了,为了找到那个刷了一半的页,就需要比对header和tailer中的校验和,如果不一致就代表同步发生了错误。
- 后4个字节代表页面被最后修改时对应的日志序列位置(LSN),这个部分也是为了校验页的完整性的。