InnoDB的行格式和页结构
InnoDB行格式
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘存放的方式也被称为行格式,InnoDB目前有4种行格式,分别是Compact,Redundant,Dynamic,Compressed
- Compact行格式

变长字段长度列表:逆序存放变长字段真实长度
NULL值列表:逆序存放,1表示null,0表示不为null,长度为字节的整数倍,不够则高位补0
记录头信息:

另外,每一行是有三个隐藏字段的:隐藏主键,最近修改事务id,回滚指针,所以,一行记录的完整的样子如下:

- Redundant行格式

- 字段长度偏移列表:逆序存放所有列的长度信息(以偏移量的形式),通过两两做差求出真实长度
- 偏移量的二进制数的第一个比特位表示是否为null、
为什么都要逆序存放?
从上面的记录头信息图中可以看出,一条记录的记录头信息中有一个指向下一条记录的指针,这个指针指向的位置是下一条记录的额外信息和真实数据之间的位置,这样的好处是,向左读取的字段的信息的顺序跟向右读取的字段的真实数据的顺序相同
被删除的记录如何处理?
从上面的记录头信息中看到,有一个属性代表记录是否被删除,当我们delete一条记录的时候,其实只是修改了这个标志位,并没有被真正删除,这些记录之所以不立即从磁盘上删除,是因为在删除之后,其他记录在磁盘上需要重新排列,这对性能有消耗,所以只是打上标记,然后将被删除的记录串成一个垃圾链表,垃圾链表中的记录占用的空间称为可重用空间,之后插入新的记录是,可以覆盖这些空间
MySQL是由延迟清理机制的,也就是并不第一时间删除垃圾记录,而是延迟一段时间才清理
延迟清理的优点:
- 延迟清理能够提高数据库性能,避免了大量的IO操作
- 减少了磁盘碎片,立即删除时会产生大量碎片,延迟清理能够减少磁盘碎片,并提高读写效率
延迟清理的缺点:
- 占用磁盘空间,不删除数据而只是标记,数据仍然存在于磁盘中,垃圾数据占用了磁盘空间,可以定期清理已删除的数据:使用
OPTIMIZE TABLE
,会重新排列表,删除已删除的数据
行溢出
MySQL中数据存在磁盘中,而真正处理数据的过程是在内存中,所以需要把磁盘中的数据加载到内存中,磁盘读写的速度和内存相比,差距很大,所以我们获取记录时不是一条条的从磁盘读取记录,而是以页作为磁盘和内存交互的基本单位,一页中有很多条记录,InnoDB中页的大小一般为16KB
行溢出:MySQL中一页16k,而varchar最大容量约为64k,当数据很大,装不下时,就会发生行溢出
对于Compact和Redundant行格式,发生行溢出时,列存放的是部分数据+指向其他页的指针,将溢出的数据存放在其他页
Dynamic和Compressed行格式和Compact基本相同,行溢出时,它们的列存放指向其他页的指针,不存放数据
Compressed行格式相比于Dynamic,Compressed采用了压缩算法对页面进行压缩,更节省空间
InnoDB索引页结构
InnoDB为了不同的目的而设计了许多不同类型的页,比如存放undolog的undo页,存放数据的索引页,也可以叫做数据页
MySQL中,一页16k,这16k的索引页被划分为多个部分,不同部分有不同的功能

从图中可以看出,索引页被划分为了7个部分

刚开始生成页的时候,其实并没有user records
这部分,当我们插入数据的术后,会从free space划分出一块到user records部分,当free space空间全部被替换掉之后,就说明这一页用完了,需要去申请新的页
File Header+File Trailer
MySQL有很多类型的页,File Header和File Trailer中记录的是各种类型的页都通用的信息,不同类型的页都会以File Header作为第一个组成部分,File Trailer作为最后一个部分,下图是File Header中记录的内容

什么是校验和?校验和有什么用处?
- 什么是校验和?
简单来说,页的校验和就代表这个页。举个例子,比如一个很长的字符串,现在通过某种算法,计算出一个比较短的值,这就是它的校验和,校验和能够代表这个很长的字符串,当需要与另一个很长的字符串比较是否相等时,只用比较两个字符串的校验和就可以了,这样效率更高
- 校验和有什么用处?
我们知道InnoDB是把数据存储到磁盘上的,但是磁盘内存速度差距太大,需要以页为单位进行交互,如果内存中的页被修改,会被标记为脏页,在之后的某个时间会将数据同步到磁盘中,如果在同步到一半的时候断电了,因此校验和用来检测一个页是否完整
- 校验和是如何工作的?
File Header和File Trailer中都有校验和,每当一个页面在内存中修改了,在同步到磁盘之前,要把它的校验和算出来,因为File Header在页面的前面,所以校验和会被首先同步到磁盘,当页完全同步成功时,校验和也会被写在File Trailer中,这样,文件头和文件尾的校验和就是一致的;如果写了一半发生了断电,那么文件头和文件尾的校验和是不一致的,文件头中的校验和代表已经修改过的页面,而文件尾中的校验和代表原来的页面,二者不一致,代表同步中出现了错误
Page Header
Page Header是专门针对索引页的,用来记录索引页中存储的记录的状态信息

最大+最小记录
每个索引页都会被自动加上最大,最小两条记录,这两条记录并不是我们自己插入的数据,最大+最小记录是两个虚拟记录,MySQL中记录与记录之间形成一个链表,最小记录的下一条记录是本页中主键值最小的记录,本页中那个主键值最大的记录的下一条记录是最大记录

Page Directory
由上图可知,记录在页中按照主键值由小到大的顺序链接成了一个单链表,如果想根据主键查找记录的时候应该则怎么办?
最简单的方法:遍历链表,这种方法显然是不可取的,当数据多的时候,遍历链表要花费大量的时间
平常看书的时候,先看目录,然后根据目录找到对应的内容,Page Directory(页目录)就相当于记录的目录
Page Directory的生成过程如下:
- 将所有正常的记录(包括最大、最小记录,不包括标记为已删除的记录)划分为几个组
- 每个组的最后一条记录(组内主键最大的记录)的头信息中的
n_owned
表示该组内共有几条记录 - 将每个组的最后一条记录的地址偏移量提取出来,按照顺序存储到页目录中,页目录中的这些地址偏移量称为
槽

InnoDB规定:
- 最小记录所在分组只能有一条记录
- 最大记录所在分组中的记录条数在1-8条之间
- 其他分组中的记录条数在4-8条之间
在一个索引页中查找指定主键值的记录的过程分为两步:
- 二分法确定该记录所在的槽,即找到该记录所在的分组,并找到该槽中主键最小的记录
- 通过记录的
next_record
遍历该组记录,最后找到目标记录