【MySQL进阶】深入理解InnoDB记录结构
参考资料:《MySQL是怎么运行的:从根儿上理解MySQL》。
前言:
我们一般使用的MySQL关系型数据库,更是经典中的经典,虽说MySQL已经非常成熟,但对于MySQL的掌握程度,如果我们只停留在使用层面,不了解它的底层设计,那咱永远只能停留在写SQL上,成为一个彻头彻尾的CRUDBoy。
一:InnoDB页简介
InnoDB 是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢,和内存读写差了几个数量级,所以当我们想从表中获取某些记录时, InnoDB 存储引擎需要一条一条的把记录从磁盘上读出来么?不,那样会慢死,InnoDB 采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
二:InnoDB行格式
行格式简介
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为 行格式 或者 记录格式 。
InnooDB目前有四种行格式:
- compact(简洁的)
- redundant(冗余的)
- dynamic(动态的)
- compress(压缩的)
创建表可以这样指定它的 行格式 :
mysql> CREATE TABLE record_format_demo (
-> c1 VARCHAR(10),
-> c2 VARCHAR(10) NOT NULL,
-> c3 CHAR(10),
-> c4 VARCHAR(10)
-> ) CHARSET = ascii ROW_FORMAT = COMPACT;
COMPACT行格式
废话不多说,直接看图:
一条完整的记录其实可以被分为 记录的额外信息 和 记录的真实数据 两大部分
变长字段长度列表
MySQL 支持一些变长的数据类型,比如 VARCHAR(M) 、 VARBINARY(M) 、各种 TEXT 类型,各种 BLOB 类型,变长字段中存储多少字节的数据是不固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存起来,这样才不至于把 MySQL 服务器搞懵。
在 Compact 行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放,我们再次强调一遍,是逆序存放!
如上图,其 变长字段长度列表 的效果为:
另外需要注意的一点是,变长字段长度列表中只存储值为 非NULL 的列内容占用的长度,值为 NULL 的列的长度是不储存的 。
NULL值列表
如果表中没有允许存储 NULL 的列,则 NULL值列表 也不存在了,否则将每个允许存储 NULL 的列对应一个二进制位,二进制位按照列的顺序逆序排列,二进制位表示的意义如下:
- 二进制位的值为 1 时,代表该列的值为 NULL 。
- 二进制位的值为 0 时,代表该列的值不为 NULL 。
- MySQL 规定 NULL值列表 必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补 0 。
所以这两条记录在填充了 NULL值列表 后的示意图就是这样:
记录头信息
用于描述记录的记录头信息,它是由固定的5个字节组成。也就是40个二进制位,不同的位代表不同的意思。
这些二进制位代表的详细信息如下表:(只需要看一遍混个脸熟,等之后用到这些属性的时候我们
再回过头来看)
记录的真实数据
对于 record_format_demo 表来说, 记录的真实数据 除了 c1 、 c2 、 c3 、 c4 这几个我们自己定义的列的数据以外, MySQL 会为每个记录默认的添加一些列(也称为 隐藏列 ),具体的列如下:
InnoDB 表对主键的生成策略:
优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个 Unique 键作为主键,如果表中连 Unique 键都没有定义的话,则 InnoDB 会为表默认添加一个名为row_id 的隐藏列作为主键。
因为表 record_format_demo 并没有定义主键,所以 MySQL 服务器会为每条记录增加上述的3个列。现在看一下加上 记录的真实数据 的两个记录长什么样吧:
CHAR(M)列的存储格式
record_format_demo 表的 c1 、 c2 、 c4 列的类型是 VARCHAR(10) ,而 c3 列的类型是 CHAR(10) ,我们说在Compact 行格式下只会把变长类型的列的长度逆序存到 变长字段长度列表 中。
但是这只是因为我们的 record_format_demo 表采用的是 ascii 字符集,这个字符集是一个定长字符集,也就是说表示一个字符采用固定的一个字节,如果采用变长的字符集(也就是表示一个字符需要的字节数不确定,比如gbk 表示一个字符要12个字节、 utf8 表示一个字符要13个字节等)的话, c3 列的长度也会被存储到 变长字段长度列表 中。
这就意味着:对于 CHAR(M) 类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到变长字段长度列表。
另外有一点还需要注意,变长字符集的 CHAR(M) 类型的列要求至少占用 M 个字节,而VARCHAR(M) 却没有这个要求。比方说对于使用 utf8 字符集的 CHAR(10) 的列来说,该列存储的数据字节长度的范围是10~30个字节。即使我们向该列中存储一个空字符串也会占用 10 个字节,这是怕将来更新该列的值的字节长度大于原有值的字节长度而小于10个字节时,可以在该记录处直接更新,而不是在存储空间中重新分配一个新的记录空间,导致原有的记录空间成为所谓的碎片。
Redundant行格式
其实知道了 Compact 行格式之后,其他的行格式就是依葫芦画瓢了。我们现在要介绍的Redundant 行格式是MySQL5.0 之前用的一种行格式,也就是说它已经非常老了。
————暂时不记录…
Dynamic和Compressed行格式
下边要介绍另外两个行格式, Dynamic 和 Compressed 行格式,我现在使用的 MySQL 版本是 5.7,它的默认行格式就是 Dynamic ,这俩行格式和 Compact 行格式挺像,只不过在处理 行溢出 数据时有点儿分歧,它们不会在记录的真实数据处存储字段真实数据的前 768 个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址,就像这样:
Compressed 行格式和 Dynamic 不同的一点是, Compressed 行格式会采用压缩算法对页面进行压缩,以节省空间。
三:总结
1:页是 MySQL 中磁盘和内存交互的基本单位,也是 MySQL 是管理存储空间的基本单位。
2:InnoDB 目前定义了4种行格式
- COMPACT行格式
- Redundant行格式
- Dynamic和Compressed行格式
- 这两种行格式类似于 COMPACT行格式 ,只不过在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字符串的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。
- 另外, Compressed 行格式会采用压缩算法对页面进行压缩。
3:一个页一般是 16KB ,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为 行溢出 。