InnoDB之前版本为Antelope文件格式,支持Compact和Redundant行记录格式。在InnoDB 1.0.X版本开始引入新的文件格式Barracuda,除了支持以上两种记录格式外,还支持Compressed和Dynamic。即InnoDB支持以下四种行记录格式:
Compact是较为经典的行记录格式,后出现的两种记录格式也是在Compact的基础上改进优化,如对BLOB数据采用完全溢出方式存储,Compressed会对存储进行压缩等。所以主要介绍Compact存储格式。
在mysql5.7中,默认开启每个表有独立表空间的配置,即对于创建每一个数据表,在mysql的存储目录下,都对应一个.ibd文件记录该表的数据信息。以下两条命令在下面的例子中有用。
-- 查看mysql的数据存储目录
show global variables like "%datadir%"
-- 在目录下将.ibd文件转换为16进制文件查看
hexdump -C -v mytest.ibd > mytest.txt
以下的内容与《MySQL技术内幕—InnoDB存储引擎》书中的介绍稍有出入,推测是版本的问题。以下的结论仅是在ubuntu系统下,使用MySQL5.7版本测试所得。如有问题欢迎讨论。
(下文的所提到的长度均是指字节的长度)
该部分存放所有非NULL变长字段(以varchar为代表)的字节长度。如果没有非NULL变长字段,则省略这部分。
需要注意的时,这部分是以逆序的方式记录长度的(涉及大端、小端存储问题),如03 02 01代表的是第一个变长字段长度为1,第二个变长字段长度为2,第三个变长字段长度为3.至于变长字段怎么确定的序列,是根据创建的先后来规定的,即第一个创建的变长字段为第一个变长字段。
针对varchar类型,mysql规定在一个表在声明时,所有varchar类型的理论上长度总和不能超过65535(还存在额外的开销,实际要小于这个值,为65532,这里姑且认为上限为65535)。如果超过65535并且没有将SQL_MODE设为严格模式,将会把varchar类型自动转为text类型。
-- 即创建表时,以下设置是非法的,因为a和b两个varchar字段的长度和大于65535
create table tab1(
a varchar(30000),
b varchar(40000)
);
-- 以下创建则可以成功
create table tab1(
a varchar(30000),
b varchar(30000)
);
由此,可以推算任何一个varchar字段最大长度不会超过65535,即用2字节(16bit)就足以表示它的长度。所以在变长字段长度列表中,对每一个变长字段长度的存储,用1或2个字节。经过测试,得到以下的结论:
因为在不同的编码下,字符和字节的对应是不同的,比如GBK下,一个汉字由两个字节表示,UTF-8下是三个字节表示。而在mysql4.X中,就规定了不管是varchar(x)还是char(x)这里的x都表示字符数。这就带来了一个问题,char的长度也是不固定的了。实际证明,在Compact行记录格式中,对于字符字节不是1:1的编码方式下,将char也视为变长度字段。以声明字段为char(10)为例,即这个字段可以存10个字符:
记录头信息共占40bit,5字节空间,具体每个bit位代表的内容如下图所示。
每一条记录都会有一个RowID,
Transaction ID固定6字节
Roll Point固定7字节
这两个部分与InnoDB实现MVCC有关,版本控制、事务回滚等内容,这里不详述。
这里即按相应编码方式,按顺序存放个列的数据。在上面已经提到,这里会出现行溢出的现象。
在InnoDB下使用Compact行记录格式,utf8编码。
表中共有5个字段(t1-t5),类型如图所示,无其他约束。
存入如图所示的一行记录,在.ibd文件中查出这一行的十六进制存储内容,如图。
上面有提到,在变长字段长度列表中,当长度大于8098时,其长度记录为0xC314,这是发生了行溢出而至。InnoDB页的大小默认为16k,若要保证在一页中是一个树的结构,必须要至少两行记录。
而经过测试,一行中的varchar字段的实际总长度的阈值为8098字节,所有varchar字段实际存的超过8098字节,会发生行溢出。
在本页中会优先存储后面的字段,所以从后往前,第一个超过8098字节的字段以及其前面的字段,都只在本页中保留768字节的前缀和20个字节的偏移量(指向行溢出页地址)。
举个例子,如下的表中:
CREATE TABLE mytest1 (
t1 VARCHAR ( 5000 ),
t2 VARCHAR ( 5000 ),
t3 VARCHAR ( 5000 ) ) CHARSET = latin1 ROW_FORMAT = compact;
插入一行数据:
INSERT INTO mytest1
VALUES
(
REPEAT( 'a', 4500 ),
REPEAT( 'b' ,4500),
REPEAT( 'c', 4500 )
);
这时,在一页中会保存4500个c,768个b和768个a。(因为在保存b时4500+4500>8098了)。通过观察mytest1.ibd验证一下:
通过py_innodb_page_info.py脚本查看页的使用情况:
查看变长字段(从0000c078开始)长度列表为94 91 14 c3 14 c3 ,所以
在B-tree Node页中:
t3长度为0x9194=4500(10),即保存了4500个c,
t2和t1为0xc314,溢出,即在本页保存了768个b和768个a
而其余的b和a分别存在了两个Uncompressed BLOB Page中。
Redundant格式是MySQL5.0之前InnoDB使用的行记录存储方式,其格式如图所示:
其中,字段长度偏移列表记录了所有字段的相对位置信息。记录头信息也较Compact略微有变化
MySQL 5.7版本默认使用Dynamic格式,这两种记录格式与Compact一致,而在Compact的基础上,对行溢出时做了改进。即对于存放BLOB、大varchar等导致行溢出的字段时,采取的是完全行溢出方式。对溢出字段不再在B-tree Node页中存放768个前缀字节,而是只保存20个字节的Uncompressed BLOB Page的偏移量,所有的数据均保存在Uncompressed BLOB Page页中。
此外compressed存储行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据都能够进行有效的存储。