InnoDB提供了4种行格式供我们选择,分别是Compact、Redundant、Dynamic和Compressed行格式。
我们建表的时候,可以指定某种行格式:
CREATE TABLE table_name (列信息) ROW_FORMAT=行格式名称
也可以修改已经存在的表的行格式:
ALTER TABLE table_name ROW_FORMAT=行格式名称
为了后面的故事可以顺利展开,我们先来建一张表:
CREATE TABLE hero(
`x` VARCHAR(10),
`y` VARCHAR(10) NOT NULL,
`z` CHAR(10),
`t` VARCHAR(10)
)CHARSET=ASCII, ROW_FORMAT=COMPACT;
我建了一张表,指定的行格式是COMPACT,采用的字符集是ASCII,也就是我们的中文是无法存进去的,现在我要向这张表添加两行数据:
INSERT INTO hero(x, y, z, t) VALUES('a', 'bb', 'cccc', 'ddddd'), ('a', 'b', NULL, NULL);
现在表中的数据是这样的:
表建好了,数据填充好了,下面我们就来分析下在COMPACT行格式下,数据是如何存储的吧。
一、COMPACT行格式
从上图可以看到,一行数据被分为了两个部分,一部分是记录的额外信息,一部分是记录的真实数据。
记录额外的信息
变长字段字节数列表
varchar(X)和char(X)的区别是什么,相信大家都非常清楚,char是定长的,varchar是变长的,变长字段中存储多少字节的数据不是固定的,所以InnoDB在存储数据的时候,会把这些数据占用的真实字节数也保存下来,也就是变长字段是占用了两部分空间来存储的:
- 真实的数据内容。
- 占用的字节数。
在COMPACT行格式中,把所有的变长字段所占用的字节数逆序排放在变长字段字节数列表中。
我们先前创建了一张表,还准备了两条数据,现在我们来看下第一条数据中的变长字段字节数列表是什么怎样的。
表中有四个字段,其中x,y,t三个字段都是变长字段,所以这三个字段的字节数需要保存在变长字段字节数列表,数据表采用的字符集是ascii,所以每一个字符占用的字节数是1,下面我们来看下第一条数据各个变长字段所占用的字节数:
字段名称 | 内容 | 占用字节数 (十进制) | 占用字节数 (十六进制) |
---|---|---|---|
x | a | 1 | 0x01 |
y | bb | 2 | 0x02 |
t | ddddd | 5 | 0x05 |
所以,第一行数据x,y,t三个字段所占用的字节数分别是1 2 5,但是InnoDB会把所占用的字节数逆序排放,如果用16进制来表示变长字段所占用的字节数就是这样的效果了:
为了更容易理解、清晰,所以我用了空格来分割,其实是没有的。
由于数据的长度都比较小,用一个字节就可以表示,但是如果变长字段占用的字节数比较多,就要用两个字节来表示了,到底使用一个字节来表示,还是用两个字节来表示,InnoDB有着自己的一套规则。在说这个规则之前,要先说明下规则中用到的三个变量:
- W:指定字符集下,一个字符最多需要占用的字节数。比如,ascii字符集的W是1,GBK字符集的W是2,utf-8字符集的W是3。
- M:最多可以存储多少个字符,varchar(50)的M就是50。
- L:实际存储字符占用了多少字节。
W * M:指定字段类型、字符集下,存储的字符串最多占用的字节数。
下面就是规则了:
(1) 如果M*W<=255,那么用一个字节表示字符串所占用的字节数。
(2) 如果M*W>255,则分为两种情况:
① 如果L<=127,则用一个字节来表示字符串所占用的字节数。
② 如果L>127,则用两个字节来表示字符串所占用的字节数。
总结起来就是:该可变字段允许存储的最大字节数(W*M)>255,且真实存储的字节数(L)超过127,就用两个字节来表示字符串所占用的字节数,否则用一个字节来表示字符串所占用的字节数。
我们再来看看第二条数据,字段t的值是NULL,变长字段字节数列表只存储非NULL列内容占用的字节数,所以对于第二条数据,变长字段字节数列表只要存储x和y所占用的字节数即可,填充在变长字段字节数列表的效果是这样的:
变长字段字节数列表不是必须的,如果一个表中所有的字段都不是变长的,那么就没有变长字段字节数列表了。
我们建的表采用的字符集是ascii编码的,一个字符所占用的字节固定是1,如果我们采用utf-8字符集,一个字段所占用的字节就不是固定的了,而是一个范围:1-3,所以如果我们采用这样的字符集,char(m)虽然是定长字段,但是也会被加入到变长字段字节数列表中。
NULL值列表
有一些公司对表设计有非常明确的规定,其中有一条是任何字段都不允许为NULL,这是为什么呢?
如果表中有字段允许为NULL,InnoDB就会开辟一块空间来标识每个字段实际存储的数据是不是为NULL,如果表中的字段都不允许为NULL,那么这块空间就不复存在了。
那么InnoDB开辟出来的那块空间具体是怎么回事呢,接下去往下看。
每个允许存储为NULL的字段对应一个二进制位:
- 如果字段实际存储的数据不为NULL,二进制是0。
- 如果字段实际存储的数据是NULL,二进制是1。
这里和变长字段字节数列表是一样的,是逆序排放的。
我们新建的hero表有三个字段都允许为NULL,所以存在NULL值列表。
我们先来看第一条数据,三个字段存储的实际数据都不为NULL,所以用二进制来表示是这样的:
但是InnoDB是用整数字节的二进制位来表示NULL值列表的,现在不足8位,所以要在高位补0,最终用二进制来表示是这样的:
所以,对于第一条数据,NULL值列表用十六进制表示是0x00。
我们再来看看第二条数据,其中z和t两个字段存储的实际数据都是NULL,我们来看看用二进制如何来表示:
同样的,需要高位补0:
所以,对于第二条数据,NULL值列表用十六进制表示是0x06。
我们把两条数据的NULL值列表都填充完毕是这样的效果:
记录头信息
记录头信息中包含的内容很多,我先随便列举几条:
- delete_mask :标识此条数据是否被删除。
- next_record:下一条数据的位置。
- record_type:表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录
记录真实数据
对于hero表来说,记录真实数据部分除了我们定义的四个字段,还有三个隐藏字段,分别为:row_id、trx_id、roll_pointer,我们来看下这三个字段是什么。
row_id