参考资料:《MySQL是怎么运行的:从根儿上理解MySQL》。
数据页代表的这块 16KB 大小的存储空间可以被划分为多个部分,不同部分有不同的功能,各个部分如图所示:

从图中可以看出,一个 InnoDB 数据页的存储空间大致被划分成了 7 个部分,有的部分占用的字节数是确定的,有的部分占用的字节数是不确定的。下边我们用表格的方式来大致描述一下这7个部分都存储一些啥内容(快速的瞅一眼就行了):


为了故事的顺利发展,我们先创建一个表:
mysql> CREATE TABLE page_demo(
-> c1 INT,
-> c2 INT,
-> c3 VARCHAR(10000),
-> PRIMARY KEY (c1)
-> ) CHARSET = ascii ROW_FORMAT = Compact;
Query OK, 0 rows affected (0.03 sec)
这个新创建的 page_demo 表有3个列,其中 c1 和 c2 列是用来存储整数的, c3 列是用来存储字符串的。需要注意的是,我们把 c1 列指定为主键,所以在具体的行格式中InnoDB就没必要为我们去创建那个所谓的 row_id 隐藏列了。而且我们为这个表指定了 ascii 字符集以及 Compact 的行格式。所以这个表中记录的行格式示意图就是这样的:

从图中可以看到,我们特意把 记录头信息 的5个字节的数据给标出来了,说明它很重要,我们再次先把这些 记录头信息 中各个属性的大体意思浏览一下(我们目前使用 Compact 行格式进行演示):

下边我们试着向 page_demo 表中插入几条记录:
mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd');
Query OK, 4 rows affected (0.00 sec)
为了方便大家分析这些记录在 页 的 User Records 部分中是怎么表示的,我把记录中头信息和实际的列数据都用十进制表示出来了(其实是一堆二进制位),所以这些记录的示意图就是:

看这个图的时候需要注意一下,各条记录在 User Records 中存储的时候并没有空隙,这里只是为了大家观看方便才把每条记录单独画在一行中。
我们对照着这个图来看看记录头信息中的各个属性是啥意思:
delete_mask:
min_rec_mask:
**n_owned :**这个暂时保密,稍后它就是主角~
heap_no:


record_type:
next_record:



现在我们了解了记录在页中按照主键值由小到大顺序串联成一个单链表,那如果我们想根据主键值查找页中的某条记录该咋办呢?
比如说这样的查询语句:
SELECT * FROM page_demo WHERE c1 = 3;
我们平常想从一本书中查找某个内容的时候,一般会先看目录,找到需要查找的内容对应的书的页码,然后到对应的页码查看内容。设计 InnoDB 的大叔们为我们的记录也制作了一个类似的目录,他们的制作过程是这样的:
比方说现在的 page_demo 表中正常的记录共有6条, InnoDB 会把它们分成两组,第一组中只有一个最小记录,第二组中是剩余的5条记录,看下边的示意图:

从这个图中我们需要注意这么几点:


比方说我们想找主键值为6的记录
所以在一个数据页中查找指定主键值的记录的过程分为两步:
为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第
一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫 Page Header 的部分,它是页 结构的第二部分,这个部分占用固定的 56 个字节,专门存储各种状态信息,具体各个字节都是干嘛的看下表:

Page Header 是专门针对 数据页 记录的各种状态信息,比方说页里头有多少个记录了呀,有多少个
槽了呀。我们现在描述的 File Header 针对各种类型的页都通用,也就是说不同类型的页都会以 File Header 作为第一个组成部分,它描述了一些针对各种页都通用的一些信息,比方说这个页的编号是多少,它的上一个页、下一个页是谁。 这个部分占用固定的 38 个字节,是由下边这些内容组成的:

对照着这个表格,我们看几个目前比较重要的部分:
FIL_PAGE_SPACE_OR_CHKSUM
FIL_PAGE_OFFSET
FIL_PAGE_TYPE
FIL_PAGE_PREV 和 FIL_PAGE_NEXT

我们知道 InnoDB 存储引擎会把数据存储到磁盘上,但是磁盘速度太慢,需要以 页 为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,那么在修改后的某个时间需要把数据同步到磁盘中。但是在同步了一半的时候中断电了咋办,这不是莫名尴尬么?为了检测一个页是否完整(也就是在同步的时候有没有发生只同步一半的尴尬情况),设计 InnoDB 的大叔们在每个页的尾部都加了一个 File Trailer 部分,这个部分由 8 个字节组成,可以分成2个小部分:
前4个字节代表页的校验和
这个部分是和 File Header 中的校验和相对应的。每当一个页面在内存中修改了,在同步之前就要把它的校验和算出来,因为 File Header 在页面的前边,所以校验和会被首先同步到磁盘,当完全写完时,校验和也会被写到页的尾部,如果完全同步成功,则页的首部和尾部的校验和应该是一致的。如果写了一半儿断电了,那么在 File Header 中的校验和就代表着已经修改过的页,而在 File Trialer 中的校验和代表着原先的页,二者不同则意味着同步中间出了错。
后4个字节代表页面被最后修改时对应的日志序列位置(LSN)
InnoDB为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做 数据页 。
一个数据页可以被大致划分为7个部分,分别是:
每个记录的头信息中都有一个 next_record 属性,从而使页中的所有记录串联成一个 单链表 。
InnoDB 会为把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个 槽 ,存放在Page Directory 中,所以在一个页中根据主键查找记录是非常快的,分为两步:
每个数据页的 File Header 部分都有上一个和下一个页的编号,所以所有的数据页会组成一个 双链表.
为保证从内存中同步到磁盘的页的完整性,在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的 LSN 值,如果首部和尾部的校验和和 LSN 值校验不成功的话,就说明同步过程出现了问题。