一、InnoDB行的记录格式
1.0.x之前使用Compact和Redundant两种格式来存入行记录;
通过SHOW TABLE STATUS LIKE ‘tablename%’ 来查看表的行记录格式;
1、Compact格式
一个页中存放的行数据越多,性能就越高;
compact格式如下:
• 变长字段长度列表:若列的长度大于255个字节,用2个字节表示,或小于255个字节,用1个字节表示;
• NULL标志位,列中有NULL时用1表示,所占用的字节长度为1;
记录头信息:见下表:
• 最后部分记录的就是行的数据;
注意:NULL值不会占用任何空间,除了NULL标志位;除了用户定位的列外,都包含两个隐藏列:事务ID和回滚指针列,分别为6字节和7字节,若未定义主键和唯一索引列,还会定义6字节的rowid主键列;
例子:
创建一个行记录格式为compact的表:
CREATE TABLE mytest(
t1 varchar(10),
t2 varchar(10),
t3 char(10),
t4 varchar(10)
) ENGINE=INNODB CHARSET=LATIN1 ROW_FORMAT=COMPACT;
INSERT INTO mytest VALUES(‘a’,’bb’,’bb’,’ccc’);
INSERT INTO mytest VALUES(‘d’,’ee’,’ee’,’fff’);
INSERT INTO mytest VALUES(‘d’,NULL,NULL,’fff’);
windows下用UltraEdit打开mytest.ibd,找到以下信息:
03 02 01 //变长字段长度列表,逆序;
00 //NULL值标识位,第一行没有NULL值;
00 00 10 00 2C //Record Header,固定长度为5;
00 00 00 00 02 00//rowId,自动创建,6个字节;
00 00 00 00 0B 57//TransactionID,6个字节
D1 00 00 01 83 01 10//Roll Pointer,7个字节
61 //第一行第一列数据a;
62 62 //第二列数据bb;
62 62 20 20 20 20 20 20 20 20//第三行数据bb;
63 63 63 //第四列数据ccc
接着同理是第2行的信息,第三行如下:
03 01//变长字段长度列表,逆序;
06 //NULL值标识位,这行有空值
00 00 20 FF 98 //Record Header,固定长度为5;
00 00 00 00 02 02 //rowId,自动创建,6个字节;
00 00 00 00 0B 5D //TransactionID,6个字节
D5 00 00 01 87 01 10 //Roll Pointer,7个字节
64 //第三行第一列d
66 66 66 //第三行第三列fff
2、Redundant格式:
5.0版本之前的格式:
结构如下:
• 字段长度偏移列表:按列的顺序逆序存放,若列的长度小于255,用1字节表示,否则用2字节表示;
记录头信息:见下表:
记录头占6个字节,n_fields=10bit,代表列的数量,所以mysql最多支持1023列;
例子:
创建一个行记录格式为compact的表:
CREATE TABLE mytest2
ENGINE=INNODB CHARSET=LATIN1 ROW_FORMAT=REDUNDANT
AS
SELECT * FROM mytest;
同样打开mytest2.ibd,找到以下信息:
23 20 16 14 13 0C 06 //长度偏移列表,逆序,7字节;
00 00 10 0F 00 BA//Record Header,固定长度为6;
00 00 00 00 02 03 //rowId,自动创建,6个字节;
00 00 00 00 0B 63 //TransactionID,6个字节
DB 00 00 01 8B 01 10 //Roll Pointer,7个字节
61 //第一行第一列数据a;
62 62//第二列数据bb;
62 62 20 20 20 20 20 20 20 20//第三行数据bb;
63 63 63 //第四列数据ccc
其中23 20 16 14 13 0C 06 逆转后为06 0C 13 14 16 20 23,代表第一列长度为6,第二列长度为6(6+6=0xOC),第三列长度为7(6+6+7=0x13),第四列长度为1(6+6+7+1=0x14),第五列长度为2(6+6+7+1+2=0x16),第六列长度为10(6+6+7+1+2+10=0x20),第七列长度为3(6+6+7+1+2+10+2 + 3=0x23)
再看第3行为NULL值的处理
21 9E 94 14 13 0C 06//长度偏移列表,逆序,7字节;
00 00 20 0F 00 74//Record Header,固定长度为6;
00 00 00 00 02 05 //rowId,自动创建,6个字节;
00 00 00 00 0B 63 //TransactionID,6个字节
DB 00 00 01 8B 01 2C //Roll Pointer,7个字节
64 //第一行第一列数据d;
00 00 00 00 00 00 00 00 00 00//第三行NULL;
66 66 66 //第四列数据fff
21 9E 94 14 13 0C 06 逆序后得06 0C 13 14 94 9E 21,其中94代表第2列的NULL值,为VARCHAR类型,第3列的值9E代表CHAR类型的NULL值 9E=(94+10=0x9E),可见,VARCHAR类型的NULL值不占空间,而CHAR类型的NULL值需要占空间;
3、行溢出数据
InnoDB可以把一行记录的某些数据放在数据页之外,比如blob,Clob,还有varchar类型的数据等,一个varchar类型的数据在mysql中可以存储65535字节的数据,这里指的是所有varchar列的总和。但实际测试时只能存65532个字节的数据;这是在字符集为latin1时的情况,如果字符集为UTF-8或GBK,存储更少。因为varchar(N)里面的N应该指的是字符长度。
一个InnoDB的页大小为16K,即16384个字节,是存储不了65532字节的数据的,所以发生行溢出时,数据存放在页类型为Uncompress Blob页中。
例子:
CREATE TABLE t(a VARCHAR(65532)) CHARSET=LATIN1 ENGINE=INNODB;
INSERT INTO t SELECT REPEAT(‘a’,65532);
使用py_innodb_page_info查看:
D:\Python27\python.exe E:/gitrepos/pyscripts/innodb/py_innodb_page_info.py -v D:\wamp\bin\mysql\mysql5.6.17\data\test\t.ibd
page offset 00000000, page type
page offset 00000001, page type
page offset 00000002, page type
page offset 00000003, page type
page offset 00000004, page type
page offset 00000005, page type
page offset 00000006, page type
page offset 00000007, page type
Total number of page: 8:
Insert Buffer Bitmap: 1
Uncompressed BLOB Page: 4
File Space Header: 1
B-tree Node: 1
File Segment inode: 1
可以看到B-tree Node有一个,Uncompressed BLOB Page却有4个,说明溢出的数据大部分存储在Uncompressed BLOB Page中。一般数据页只存储前面的768个字节。一个页中至少要放2条数据,这个阈值是varchar长度为8098。
行溢出存储的结构如下:
4、Compressed和Dynamic格式
V1.0.x版本后引入,以前的Compact和Redundant统称为Antelope格式,新的格式称为Barracuda格式。
新的格式为于Blob数据采用完全的行溢出方式,在数据页中只存放20字节的指针,其它存储在Off Page中:
5、Char的行存储结构
一般Char(N)中的N指的是字符长度,不同的编码,字节数是不一样的,就是说char类型的内部存储实际上是不定长的。
参考《MySQL技术内幕 -InnoDB存储引擎》整理,如侵权请联系vinin@163.com。