- 将表中的数据存储到磁盘上的存储引擎
- 真正处理数据的过程发生在内存中,需要将磁盘中的数据加载到内存中
- 将数据划分为若干页,以页为磁盘与内存交互的基本单位【16KB】,系统变量innodb_page_size指明页大小,只能在第一次初始化时指定
InnoDB主键生成策略
- 优先使用用户自定义的主键作为主键
- 如果用户未定义主键,则选取一个不允许存储NULL值的UNIQUE键作为主键
- 如果表中没有不存NULL值的UNIQUE键,则默认添加一个名为row_id的隐藏列作为主键
InnoDB行格式
-
COMPACT行格式
- 所有变长字段的真实数据占用的字节数都存放在记录的开头位置,形成一个变长字段长度列表,各变长字段的真实数据占用的字节数按照列的顺序逆序存放
- 溢出列存储前768字节的数据以及一个指向其他页的地址
-
DYNAMIC行格式
- 把溢出列的所有真实数据都存储于溢出页中,只在记录的真实数据处存储20字节大小的指向溢出页的地址
-
COMPRESSED行格式
- 不同于DYNAMIC行格式的是会采用压缩算法对页面进行压缩,以节省空间
InnoDB读取记录的变长字段长度列表时先查看表结构,如果某个变长字段允许存储的最长字节数不大于255,可以认为只使用1字节来表示真实数据占用的字节数
如果某个变长字段允许存储的最大字节数大于255,怎么区分它正在读的某个字节是一个单独字段长度还是半个字段长度【使用该字节的第一个二进制位作为标志位,如果该字节第一位为1,该字节就是半个字段长度】
NULL值列表
- 一条记录中如果把NULL值都放到记录的真实数据中存储会很占地方,COMPACT行格式把一条记录中值为NULL的列统一管理起来,存储到NULL值列表中
- 1统计表中允许存储NULL的列
- 2将每个允许存储NULL的列对应一个二进制位,二进制位按列的逆序排列,1代表该列的值为NULL
- NULL值列表必须用整数个字节的位来表示,不足则高位补0
溢出列
- InnoDB磁盘和内存交互的基本单位是页【一般为16K,16384字节】,如果一条数据占用字节大于一页,则第一页外的数据页面称为溢出页
- 对于COMPACT和REDUNDANT行格式来说,会存储前n字节的数据以及一个指向其他页的地址
InnoDB数据页结构

记录头信息
-
heap_no:13比特,表示当前记录在页面堆中的相对位置
-
-
每新申请一条记录的存储空间时,该条记录比物理位置在它前面那条记录的heap_no值大1
-
heap_no从2开始,记录页面中的最小记录(Infimum记录)和页面中的最大记录(Supremum记录),称为伪记录或虚拟记录,在页面中的相对位置最靠前
-
记录的大小就是比较主键的大小
-
无论向页中插入多少条记录,规定任何用户记录都比Infimum记录大,任何用户记录都比Supremum记录小
-
不存放在页的User Records部分,而是单独放在一个称为Infimum+Supremum的部分
-
堆中记录的heap_no值在分配后就不再发生变动,即使删除了堆中的某条记录,这条记录的heap_no值也保持不变
-
-
record_type:3比特,表示当前记录的类型,0表示普通记录,1表示B+树非叶节点的目录项记录,2表示Infimum记录,3表示Supremum记录
-
next_record:16比特,表示从当前记录的真实数据到下一条记录的真实数据的距离,正值在后负值在前,使页中记录串联成一个单向链表
-
-
删除记录后,该记录deleted_flag设为1,next_record值变为0,前一条记录的next_record指向删除记录的next_record
-
为什么要指向记录头信息和真实数据之间的位置?
- 向左读取记录头信息,向右读取真实数据
- 变长字段长度列表、NULL值列表中的信息都是逆序存放的,这样可以使记录中位置靠前的字段和它们对应的字段长度信息在内存中的距离更近,可能会提高高速缓存的命中率【频繁操作的数据都会被放在前面,相当于提高访问优先级】
-
-
deleted_flag:1比特,标记该记录是否被删除
- 删除记录后,信息还保留在真实的磁盘上,因为移除他们后还需要在磁盘上重新排列其他的记录,会带来额外的性能消耗
- 所有被删除的记录会组成一个垃圾链表,记录在这个链表中占用的空间称为可重用空间,如果有新记录插入到表中,他们就可能覆盖掉被删除的记录占用的存储空间【新记录覆盖】
-
min_rec_flag:1比特,B+树中每层非叶子节点中的最小的目录项都会添加该标记
Page Directory页目录
-
记录在页中是按照主键值有小到大的顺序串联成一个单向链表,如果查找都要某条数据从头开始遍历就很笨
-
InnoDb数据页结构的page directory就类似于图书页目录
- 将所有正常的记录【包括Infimum和Supremum记录,但不包括垃圾链表中的删除记录】划分为几组
- 每组最后一条记录【最大】相当于"大哥","大哥"记录的头信息中的n_owned属性表示该组有几条记录
- 将每组最后一条记录在页面中的地址偏移量【该记录的真实数据与页面中第0个字节之间的距离】单独提取出来,按顺序存储到靠近页尾部的地方【Page Directory】页目录中的这些地址偏移量称为槽(slot),每个槽占用2字节,页目录由多个槽组成
-
- 页目录中有2个槽,即记录被分成了2组,槽1中的值为112【Supremum记录在页面中的地址偏移量】,槽0中的值为99【Infimum记录的地址偏移量】
- Infimum记录的n_owned值为1,以Infimum记录为最后一个节点的分组只有一条记录,即它本身
- Supremum记录的n_owned值为5,以Supremum记录为最后一个节点的分组有5条记录,即除了它本身还有4条记录
- 每个槽占用2字节,按照对应记录的大小相邻分布,槽对应的记录越小,它的位置越靠近File Trailer
-
-
每个分组记录条数规定
- 对于Infimum记录所在的分组只能有1条记录
- Supremum记录所在的分组只能有1-8条记录
- 剩下的分组中记录条数范围只能在4-8之间
记录分组
-
-
初始情况下,一个数据页只有Infimum记录和Supremum记录,分属两组,页目录也只有两个槽,分别代表它们在页面中的地址偏移量
-
之后每插入一条记录,都会从页目录中找到对应记录的主键值比待插入记录的主键值大且差值最小的槽【本质上,槽是一个组内最大的那条记录在页面中的地址偏移量,通过槽可以快速找到对应的记录主键值】,然后把该槽对应的记录的n_owned值+1,直到该组记录数=8
-
当一个组记录数=8后,再插入一条记录,会将组中的记录拆分成两个组,其中一个组4条记录,另一个5条记录,并在页目录中新增一个槽,记录这个新增分组中最大的那条记录的偏移量
查找指定主键值的记录
-
通过二分法确定该记录所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录
-
通过记录的next_record属性遍历该槽所在的组中的各个记录
Page Header-数据页专有信息56字节
-
为了得到数据页中的记录的状态信息【多少条、Free Space在页面中的地址偏移量、页目录中存储了多少个槽等】
-
PAGE_DIRECTION:如果新插入的一条记录主键值比上一条记录的主键值大,插入方向为右边,反之则是左边。用来表示最后一条记录插入方向的状态
-
PAGE_N_DIRECTION:假设连续几次插入新记录的方向一致,记录条数,如果最后一条记录的插入方向发生改变,这个状态值会被清零后重新计算
File Header文件头部-38字节
-
通用于各类型的页,各种类型的页都会以File Header作为第一个组成部分
-
描述了一些通用于各种页的信息
-
FIL_PAGE_SPACE_OR_CHKSUM:当前页面的校验和checksum,校验和不同,则两个长字节串肯定不同,省去直接比较两个长字节串的时间损耗
-
FIL_PAGE_OFFSET:每个页都有一个单独的页号
-
FIL_PAGE_TYPE:表示当前页的类型
FIL_PAGE_INDEX,索引页是用来存放记录的数据页类型
- FIL_PAGE_PREV和FIL_PAGE_NEXT:分别代表本数据页的上一页和下一页的页号,通过建立一个双向链表就把页串联起来,但并不是所有类型页都有这两个属性,索引页【存放数据的页】有这两个属性,可组成一个双向链表
File Trailer文件尾部-8字节
- 为了检查一个页是否完整【刷新时有没有发生之刷新了一部分的情况】
- 八字节
- 前4字节代表页的校验和,与File Header中的校验和相对应,页面在内存发生修改时,在刷新之前把页面的校验和算出来,File Header中的校验和会被首先刷新到磁盘,完全写完后校验和也会被写入Trailer,如果刷新成功,则页首和页尾的校验和英国一致,如果不一致则意味着刷新期间发生了错误
- 后4字节代表页面被修改时对应的LSN的后4字节,用于校验页的完整性