01.真实数据在不同存储引擎中存放的格式一般是不同的
MySQL 服务器上负责对表中数据的读取和写入工作的部分是 存储引擎 ,而服务器又支持不同类型的存储引擎,比如 InnoDB 、 MyISAM 、 Memory ,不同的存储引擎一般是由不同的人为实现不同的特性而开发的,真实数据在不同存储引擎中存放的格式一般是不同的,甚至有的存储引擎比如 Memory 都不用磁盘来存储数据,也就是说关闭服务器后表中的数据就消失了。
由于 InnoDB 是 MySQL 默认的存储引擎,也是我们最常用到的存储引擎,我们也没有那么多时间去把各个存储引擎的内部实现都看一遍,所以本集要唠叨的是使用 InnoDB 作为存储引擎的数据存储结构,了解了一个存储引擎的数据存储结构之后,其他的存储引擎都是依葫芦画瓢
02.InnoDB 是一个将表中的数据存储到磁盘上的存储引擎
InnoDB 是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。
而我们知道读写磁盘的速度非常慢,和内存读写差了几个数量级,所以当我们想从表中获取某些记录时, InnoDB 存储引擎需要一条一条的把记录从磁盘上读出来么?
不,那样会慢死,InnoDB 采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
03.InnoDB数据存储结构之行格式
我们平时的数据都是以行记录为单位向表中插入数据的,这些记录在磁盘上的存放形式也被称为 行格式或者 记录格式。
设计 InnoDB 存储引擎的大叔到现在为止设计了 4 中不同类型的行格式,分别是 COMPACT、REDUNDANT、DYNAMIC 和 COMPRESSED。随着时间的推移,它们可能会设计出更多的行格式,但是不管怎么变,这些行格式在原理上大体都是相同的。
3.1 行格式的语法
我们可以在创建或者修改表的语句中指定记录所使用的行格式:
# 创建表时指定行格式
CREATE TABLE 表名(列的信息) ROW_FORMAT = 行格式名称
# 修改表的行格式
ALTER TABLE 表名 ROW_FORMAT = 行格式名称
例子:
3.2 COMPACT 行格式
COMPACT 表示紧凑的,在 MySQL 5.1 版本中,默认设置为 COMPACT 行格式。
一条完整的记录可以分为记录的额外信息和记录的真实数据两大部分:
3.2.1 记录的额外信息
这部分信息时服务器为了更好地管理记录而不得不额外添加的一些信息。这些额外的信息分为三部分,分别是变长字段长度列表、NULL 值列表和记录头信息。
01、变长字段长度列表
MySQL 支持一些变长的数据类型,比如 varchar(M)、varbinary(M)、text 类型、blob 类型,这些数据类型修饰列称为变长字段,变长字段中存储多少字节的数据不是固定的,所以我们在存储真实数据的时候,需要顺便把这些数据占用的字节数也存起来。
也就是说,这些变长字段占用的存储空间分为两部分:
- 真正的数据内容;
- 该数据占用的字节数。
在 Compact 行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放,我们再次强调一遍,是逆序存放。
例子:
我们拿 record_format_demo 表中的第一条记录来举个例子。因为 record_format_demo 表的 c1 、 c2 、 c4 列都是 VARCHAR(10) 类型的,也就是变长的数据类型,所以这三个列的值的长度都需要保存在记录开头处,因为record_format_demo 表中的各个列都使用的是 ascii 字符集,所以每个字符只需要1个字节来进行编码,来看一下第一条记录各变长字段内容的长度:
又因为这些长度值需要按照列的逆序存放,所以最后 变长字段长度列表 的字节串用十六进制表示的效果就是(各个字节之间实际上没有空格,用空格隔开只是方便理解):
01 03 04
ps:总结一下就是说:如果该可变字段允许存储的最大字节数( M×W )超过255字节并且真实存储的字节数( L )
超过127字节,则使用2个字节,否则使用1个字节。
由于第一行记录中 c1 、 c2 、 c4 列中的字符串都比较短,也就是说内容占用的字节数比较小,用1个字节就可以表示,但是如果变长列的内容占用的字节数比较多,可能就需要用2个字节来表示。具体用1个还是2个字节来表示真实数据占用的字节数, InnoDB 有它的一套规则,我们首先声明一下 W 、 M 和 L 的意思:
所以确定使用1个字节还是2个字节表示真正字符串占用的字节数的规则就是这样:
总结一下就是说:如果该可变字段允许存储的最大字节数( M×W )超过255字节并且真实存储的字节数( L )超过127字节,则使用2个字节,否则使用1个字节。
另外需要注意的一点是,变长字段长度列表中只存储值为 非NULL 的列内容占用的长度,值为 NULL 的列的长度是不储存的 。也就是说对于第二条记录来说,因为 c4 列的值为 NULL ,所以第二条记录的 变长字段长度列表 只需要存储 c1 和 c2 列的长度即可。
其中 c1 列存储的值为 ‘eeee’ ,占用的字节数为 4 , c2 列存储的值为 ‘fff’ ,占用的字节数为 3 。数字 4 可以用1个字节表示, 3 也可以用1个字节表示,所以整个 变长字段长度列表 共需2个字节。填充完 变长字段长度列表 的两条记录的对比图如下:
02.NULL值列表
我们知道表中的某些列可能存储 NULL 值,如果把这些 NULL 值都放到 记录的真实数据 中存储会很占地方,所以 Compact 行格式把这些值为 NULL 的列统一管理起来,存储到 NULL 值列表中,它的处理过程是这样的:
例子:
03.记录头信息
除了 变长字段长度列表 、 NULL值列表 之外,还有一个用于描述记录的 记录头信息 ,它是由固定的 5 个字节组成。 5 个字节也就是 40 个二进制位,不同的位代表不同的意思,如图:
04.记录的真实数据
对于 record_format_demo 表来说, 记录的真实数据 除了 c1 、 c2 、 c3 、 c4 这几个我们自己定义的列的数据以外, MySQL 会为每个记录默认的添加一些列(也称为 隐藏列 ),具体的列如下:
这里需要提一下 InnoDB 表对主键的生成策略:
优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个 Unique 键作为主键,如果表中连 Unique 键都没有定义的话,则 InnoDB 会为表默认添加一个名为row_id 的隐藏列作为主键。
所以我们从上表中可以看出:InnoDB存储引擎会为每条记录都添加 transaction_id和 roll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)。这些隐藏列的值不用我们操心, InnoDB 存储引擎会自己帮我们生成的。
例子:
因为表 record_format_demo 并没有定义主键,所以 MySQL 服务器会为每条记录增加上述的3个列。现在看一下加上 记录的真实数据 的两个记录长什么样吧:
05.CHAR(M)列的存储格式
record_format_demo 表的 c1 、 c2 、 c4 列的类型是 VARCHAR(10) ,而 c3 列的类型是 CHAR(10) ,我们说在Compact 行格式下只会把变长类型的列的长度逆序存到 变长字段长度列表 中,就像这样:
但是这只是因为我们的 record_format_demo 表采用的是 ascii 字符集,这个字符集是一个定长字符集,也就是说表示一个字符采用固定的一个字节,如果采用变长的字符集的话, c3 列的长度也会被存储到 变长字段长度列表 中,比如我们修改一下 record_format_demo 表的字符集:
mysql> ALTER TABLE record_format_demo MODIFY COLUMN c3 CHAR(10) CHARACTER SET utf8;
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
修改该列字符集后记录的 变长字段长度列表 也发生了变化,如图:
这就意味着:对于 CHAR(M) 类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到变长字段长度列表。
3.3 Redundant行格式
其实知道了 Compact 行格式之后,其他的行格式就是依葫芦画瓢了。我们现在要介绍的 Redundant 行格式是MySQL5.0 之前用的一种行格式,也就是说它已经非常老了,但是本着知识完整性的角度还是要提一下,大家乐呵乐呵的看就好。
画个图展示一下 Redundant 行格式的全貌:
现在我们把表 record_format_demo 的行格式修改为 Redundant :
mysql> ALTER TABLE record_format_demo ROW_FORMAT=Redundant;
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
为了方便大家理解和节省篇幅,我们直接把表 record_format_demo 在 Redundant 行格式下的两条记录的真实存储数据提供出来,之后我们着重分析两种行格式的不同即可。
下边我们从各个方面看一下 Redundant 行格式有什么不同的地方: