页:innodb 中一个页的大小一般是 16 Kb
数据页的结构:
用户记录(插入的数据)与 页的存储结构变化:
首先明确:
- 用户记录是存储到 User Records 中,当 user records 占满空间后(user records 由 Free Space 分配),会开始申请下一个页写入数据
-
最开始的时候,页中没有 User Records 部分,当插入数据的时候,从 Free space 分配空间给 User Records 部分,进行数据的存储
创建存储数据到 User Records 的时候,Innodb 的工作内容:
第一:记录头信息
创建表
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)
结构:
记录头的 5 字节:
插入数据后的结构:
mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd');
重点字段:
next_record:它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节便是下一条记录的真实数据。如果你熟悉数据结构的话,就立即明白了,这其实是个链表,可以通过一条记录找到它的下一条记录。但是需要注意注意再注意的一点是,下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录) ,为了更形象的表示一下这个next_record起到的作用,我们用箭头来替代一下next_record中的地址偏移量:
删除数据:
mysql> DELETE FROM page_demo WHERE c1 = 2;
变化:
- 第2条记录并没有从存储空间中移除,而是把该条记录的delete_mask值设置为1
- 第2条记录的next_record值变为了0,意味着该记录没有下一条记录了。
- 第1条记录的next_record指向了第3条记录。
- 还有一点你可能忽略了,就是最大记录的n_owned值从5变成了4
核心:
- 页目录用于记录存在多少用户记录(插入的数据)
- 页目录通过槽 + 分组的形式构建查询数据的结构
结构:
分组与槽的构建:
设计InnoDB的大佬们对每个分组中的记录条数是有规定的:对于最小记录所在的分组只能有 1 条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间。
数据查询:
通过二分法查询槽,定位到具体的用户记录(数据)
第三: Page Header(页面头部)
用于记录槽的数量,记录的状态等信息
第四: File Header(文件头部)
现在描述的File Header针对各种类型的页都通用,也就是说不同类型的页都会以File Header作为第一个组成部分,它描述了一些针对各种页都通用的一些信息,比方说这个页的编号是多少,它的上一个页、下一个页是谁啦等等~ 这个部分占用固定的38个字节,是由下面这些内容组成的:
字段:
FIL_PAGE_SPACE_OR_CHKSUM:
校验和,防止数据落盘出现中断情况,导致数据不一致
FIL_PAGE_TYPE:
FIL_PAGE_PREV和FIL_PAGE_NEXT:
InnoDB都是以页为单位存放数据的,有时候我们存放某种类型的数据占用的空间非常大(比方说一张表中可以有成千上万条记录),InnoDB可能不可以一次性为这么多数据分配一个非常大的存储空间,如果分散到多个不连续的页中存储的话需要把这些页关联起来,FIL_PAGE_PREV和FIL_PAGE_NEXT就分别代表本页的上一个和下一个页的页号。这样通过建立一个双向链表把许许多多的页就都串联起来了,而无需这些页在物理上真正连着。需要注意的是,并不是所有类型的页都有上一个和下一个页的属性,不过我们本集中介绍的数据页(也就是类型为FIL_PAGE_INDEX的页)是有这两个属性的,所以所有的数据页其实是一个双链表,就像这样:
第五:File Trailer
我们知道InnoDB存储引擎会把数据存储到磁盘上,但是磁盘速度太慢,需要以页为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,那么在修改后的某个时间需要把数据同步到磁盘中。但是在同步了一半的时候中断电了咋办,这不是莫名尴尬么?为了检测一个页是否完整(也就是在同步的时候有没有发生只同步一半的尴尬情况),设计InnoDB的大佬们在每个页的尾部都加了一个File Trailer部分,这个部分由8个字节组成,可以分成2个小部分:
前4个字节代表页的校验和
这个部分是和File Header中的校验和相对应的。每当一个页面在内存中修改了,在同步之前就要把它的校验和算出来,因为File Header在页面的前面,所以校验和会被首先同步到磁盘,当完全写完时,校验和也会被写到页的尾部,如果完全同步成功,则页的首部和尾部的校验和应该是一致的。如果写了一半儿断电了,那么在File Header中的校验和就代表着已经修改过的页,而在File Trialer中的校验和代表着原先的页,二者不同则意味着同步中间出了错。
后4个字节代表页面被最后修改时对应的日志序列位置(LSN)
这个部分也是为了校验页的完整性的,只不过我们目前还没说LSN是个什么意思,所以大家可以先不用管这个属性。
这个File Trailer与File Header类似,都是所有类型的页通用的。