InnoDB 记录存储结构
页是
MySQL
中磁盘和内存交互的基本单位,也是MySQL
是管理存储空间的基本单位。-
指定和修改行格式的语法如下:
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称 ALTER TABLE 表名 ROW_FORMAT=行格式名称
-
InnoDB
目前定义了4种行格式-
COMPACT行格式
具体组成如图:
- 变长字段长度列表存放的是每个变长字段存储的字节数,通过字符数*每个字符占用的字节来记录
- null值列表存的是该条记录哪几列为null(逆序),比如第三第四列为空,主键列NOT NULL,第二列标记为NOTNULL,则这个标志位为000001(c4)1(c3)0(0x06)
-
-
Redundant行格式
具体组成如图:
-
Dynamic和Compressed行格式
这两种行格式类似于
COMPACT行格式
,只不过在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字符串的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。另外,
Compressed
行格式会采用压缩算法对页面进行压缩。 一个页一般是
16KB
,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出
。如果某个字段长度大于了16KB,该记录在单个页面中无法存储时,InnoDB会把一部分数据存放到所谓的溢出页中。记录的真实数据处只会存储该列的前768
个字节的数据和一个指向其他页的地址,然后把剩下的数据存放到其他页中,这个过程也叫做行溢出
,存储超出768
字节的那些页面也被称为溢出页
每一条数据的结构:
这些二进制位代表的详细信息如下表:
名称 | 大小(单位:bit) | 描述 |
---|---|---|
预留位1 |
1 |
没有使用 |
预留位2 |
1 |
没有使用 |
delete_mask |
1 |
标记该记录是否被删除 |
min_rec_mask |
1 |
B+树的每层非叶子节点中的最小记录都会添加该标记 |
n_owned |
4 |
表示当前记录拥有的记录数 |
heap_no |
13 |
表示当前记录在记录堆的位置信息 |
record_type |
3 |
表示当前记录的类型,0 表示普通记录,1 表示B+树非叶子节点记录(目录项记录),2 表示最小记录,3 表示最大记录 |
next_record |
16 |
表示下一条记录的相对位置 |
页
一个InnoDB
数据页的存储空间大致被划分成了7
个部分,有的部分占用的字节数是确定的,有的部分占用的字节数是不确定的。
名称 | 中文名 | 占用空间大小 | 简单描述 |
---|---|---|---|
File Header |
文件头部 |
38 字节 |
页的一些通用信息 |
Page Header |
页面头部 |
56 字节 |
数据页专有的一些信息 |
Infimum + Supremum |
最小记录和最大记录 |
26 字节 |
两个虚拟的行记录 |
User Records |
用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space |
空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory |
页面目录 | 不确定 | 页中的某些记录的相对位置 |
File Trailer |
文件尾部 |
8 字节 |
校验页是否完整 |
记录在页中的存储
在页的7个组成部分中,我们自己存储的记录会按照我们指定的行格式
存储到User Records
部分。但是在一开始生成页的时候,其实并没有User Records
这个部分,每当我们插入一条记录,都会从Free Space
部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records
部分,当Free Space
部分的空间全部被User Records
部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了,这个过程的图示如下:
被删除的记录只是会标记为被删除,而并不会从磁盘中删除调,因为重新排列需要性能消耗。而会在新的记录插入的时候(相同id)占用到被删除的记录的空间(可重用空间)
每个页中的数据会被划分成几个组,每个组的最后一条记录的头信息中的n_owned属性表示该记录拥有多少条记录也就是这个组有多少条记录,且把他的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方(页目录,也就是每个组最后一条记录的地址偏移量存到页目录的每个槽中),页目录中的这些偏移量被称为槽。
最小记录和最大记录分别是头尾节点,并不是真实的数据记录。在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录
中新增一个槽
来记录这个新增分组中最大的那条记录的偏移量。
InnoDB
可能不可以一次性为这么多数据分配一个非常大的存储空间,如果分散到多个不连续的页中存储的话需要把这些页关联起来,FIL_PAGE_PREV
和FIL_PAGE_NEXT
就分别代表本页的上一个和下一个页的页号。这样通过建立一个双向链表把许许多多的页就都串联起来了,而无需这些页在物理上真正连着。需要注意的是,并不是所有类型的页都有上一个和下一个页的属性,不过我们本集中唠叨的数据页
(也就是类型为FIL_PAGE_INDEX
的页)是有这两个属性的,所以所有的数据页其实是一个双链表,就像这样
InnoDB
存储引擎会把数据存储到磁盘上,但是磁盘速度太慢,需要以页
为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,那么在修改后的某个时间需要把数据同步到磁盘中。
单页内查询:
根据主键查找的时候,用的是二分法,先计算中间槽的位置对应记录的主键值(这个槽的最后一条记录),用二分法找到所在的槽,然后找到该槽所在分组中主键最小的那条记录(通过找到上一个槽对应的记录的下一个节点),从那条记录开始遍历该槽所在的组中的各个记录。
多页查询:
1、定位记录所在的页(如何定位到页,就需要用到索引了)
2、在该页用单页的方法查询