0%

MySQL笔记[InnoDB]表-记录

一、InnoDB行的记录格式

1.0.x之前使用Compact和Redundant两种格式来存入行记录;

通过SHOW TABLE STATUS LIKE ‘tablename%’ 来查看表的行记录格式;

1、Compact格式

一个页中存放的行数据越多,性能就越高;

compact格式如下:

innodb10

• 变长字段长度列表:若列的长度大于255个字节,用2个字节表示,或小于255个字节,用1个字节表示;
• NULL标志位,列中有NULL时用1表示,所占用的字节长度为1;

记录头信息:见下表:

innodb11

• 最后部分记录的就是行的数据;

注意: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,找到以下信息:

innodb12

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版本之前的格式:
结构如下:

innodb13

• 字段长度偏移列表:按列的顺序逆序存放,若列的长度小于255,用1字节表示,否则用2字节表示;

记录头信息:见下表:

innodb14

记录头占6个字节,n_fields=10bit,代表列的数量,所以mysql最多支持1023列;

例子:
创建一个行记录格式为compact的表:
CREATE TABLE mytest2
ENGINE=INNODB CHARSET=LATIN1 ROW_FORMAT=REDUNDANT
AS
SELECT * FROM mytest;

同样打开mytest2.ibd,找到以下信息:

innodb15

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 level <0000>
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。

行溢出存储的结构如下:

innodb16

4、Compressed和Dynamic格式

V1.0.x版本后引入,以前的Compact和Redundant统称为Antelope格式,新的格式称为Barracuda格式。
新的格式为于Blob数据采用完全的行溢出方式,在数据页中只存放20字节的指针,其它存储在Off Page中:

innodb17

5、Char的行存储结构

一般Char(N)中的N指的是字符长度,不同的编码,字节数是不一样的,就是说char类型的内部存储实际上是不定长的。


参考《MySQL技术内幕 -InnoDB存储引擎》整理,如侵权请联系vinin@163.com