Innodb 记录的存储结构
前言
Innodb 是 MySQL 默认的存储引擎,支持事务,应用场景广。
(其他的存储引擎,例如Memory
,MyIsAm
等,自行去查资料对比)
Innodb 页简介
Innodb 存储引擎管理的数据存储在磁盘上,意味着对数据的访问和修改,都需要与磁盘进行交互。显然不可能以记录为单位进行交互,那样会慢死。
MySQL 将表数据划分为若干页,单页大小一般为16KB
。也就是说,一次性最小从磁盘中加载16KB
的数据到内存中,一次性最小将16KB
的数据从内存中刷向磁盘。
在 MySQL 进行初始化的时候,可以指定单页大小,后续则无法对其进行改变。
Innodb 行格式简介
一条记录就对应着表中的一行数据。如果数据是无规则的存放,访问和修改的效率肯定是要大打折扣的。行格式就规定了单条数据在磁盘中的存储格式。
MySQL 到目前位置已经有了4
种行格式:COMPACT、REDUNDANT、DYNAMIC、COMPRESSED
MySQL 5.7.9 之后默认的行格式是DYNAMIC
。
COMPACT 行格式
一条完整的记录 = 记录的额外信息 + 记录的真实数据
1、记录的额外信息
- 变长字段长度列表
什么是变长字段?
字段类型类似于varchar
、text
这类的,我们称作变长字段。即,实际存储的长度与规定最大存储长度可能不相等。
知道了什么是变长字段,变长字段长度列表就很容易懂了,里面记录了该条记录,所有变长字段的字节数(大小),但是不包括值为null
的字段,而且是逆序记录存储。
这里的逆序是什么意思?
比如图中的列1
,列2
,列3
,它们都是变长字段,长度分别是1
,2
,3
。那么,变长字段长度列表存储的时候就是3
,2
,1
。
为什么要逆序?
下文会说的,不要急躁。
还有一些需要注意的点:
对于变长字段来说,有些长度小,有些长度大,甚至是特别大(text
类型)。针对这些情况,单个字节肯定是描述不完全的,需要两个甚至更多。
我们先讨论1~2
个字节能够完全描述的情况,这里有个规则:如果变长字段允许存储的最大字节数超过255
字节,并且真实数据占用的字节数超过127
字节,使用2
个字节来描述字段长度,否则使用1
个字节。
那如果是针对长文本呢?
很显然,变长字段列表会膨胀,MySQL 对于长度过大的变长字段,会将一部分数据放在溢出页
中。变长字段列表只存储保留在本页中的那部分数据的一个长度。
MySQL 怎么知道要读取多少字节的长度呢?
MySQL 读取长度列表的时候,会以字节的第一个二进制位来进行判断:
0
代表只占用一个字节长度,1
代表占用两个字节长度。对于值溢出
的字段来说,会有特殊的表示方式,这里不深究。
null 值列表
和
变长字段长度列表
差不多,它会收录所有字段值运行为null
的字段,使用单个bit
位表示字段是否为null。比如说
列1
,列2
,列3
都可以为null,并且列3
的值不是null
,那在 null 值列表中的排序是0
,1
,1
。我们可以发现,也是逆序排列的。值为null的字段,并不会出现在记录的真实数据区中(没必要嘛)。字节补位操作:
null值列表必须是整数个字节单位,不满足的会进行高位补
0
。记录头信息
最复杂也是牵扯最多的部分。
由5
个字节,也就是40
个比特位组成。各部分的含义如下:
两张图的标识名称不太一样,第一张是网图,第二张是自己拍的,不过没关系,你肯定能理解的。这里就看一下,混个脸熟,后面用到的时候,再来翻阅。
2、记录的真实数据部分
(别忘了这幅图)
除了我们在表结构中定义的显式字段之外,还有2~3
个隐藏字段:
row_id
:行 id,唯一标识符,表中没有主键时自动生成trx_id
:事物 idroll_pointer
:回滚指针
混个脸熟,后面还是提及的。
这里说一下 MySQL 中主键的生成策略:
- 创建表时,在表结构中显式的定义
- 如果没有,MySQL 回去寻找表中
not null
且unique
的字段作为主键 - 以上都不满足,会自动会每条记录添加一个隐藏字段,也就是
row_id
,作为隐式主键
3、CHAR(M) 类型的存储格式
变长字段长度列表
中存储的是字节的数量。但是不同的字符集,单个字符所占用的字节数是不确定的。如果采用的是变长字符集的话,比如utf8
,那么字符所占的字节数就是不确定的。
这就意味着,对于char(m)
类型的字段来说,采用定长字符集时,不会被加入变长字段列表中
,而变长字符集时,会被加入到变长列表
中。
MySQL 还规定了,如果采用了变长字符集,CHAR(M)
至少需要占用M
个字节,也就是说会存在一定的空间浪费。
(这也是一种权衡,既想节约空间,又想尽量的减少值更新带来的碎片化问题)
溢出列
上文也提到过,如果一个文本块过大之后,就会发生溢出
现象。那这个所谓的“溢出”是指什么呢?
我们都知道,Innodb 管理数据的基本单位是页
,一般单页默认16kb
。那假设某个字段的值所占的空间大于了单页空间呢?一条记录岂不是要跨越多页?
为了避免这种现象,对于空间占用较多的列,真实记录处只存储部分数据和溢出页的指针,剩余的数据存储在其他页中,就叫做溢出页
。
一般来说,前768
字节的数据会保留在原始页中,另外再有20
字节溢出页地址信息和溢出部分所占用的字节大小。
REDUNDANT / DYNAMIC / COMPRESSED
REDUNDANT
MySQL 5.0 之前使用的一种古老的行格式,感兴趣的自行了解。
DYNAMIC 和 COMPRESSED
和
COMPACT
相似,只不过他俩在处理溢出列的时候,有所不同:它们不会在记录的真实数据处存储一部分数据,而是全部放入溢出页中,只存储20字节的地址信息和内容字节大小。
COMPRESSED 会采用一种压缩算法对页面进行压缩,以节省空间。
总结
从最基本的数据单位开始,一步一步构建知识体系。
2025/01/27
writeBy kaiven