InnoDB存储引擎和大多数数据库一样,记录是以行的形式存储的。这就意味着页中保存着表中一行行的数据。在InnoDB 1.0x版本之前,InnoDB存储引擎提供了Compact和Redundant两种格式来存放行记录数据。Redundant格式是为了兼容之前版本而保留的(mysql5.0版本之前InnoDB的行记录存储方式)。源码中使用PHYSICAL RECORD (NEW STYLE) 和PHYSICAL RECORD (OLD STYLE)来区分两种格式的。在mysql5.1版本中,默认设置为Compact行格式。可以通过命令SHOW TABLES STATUS LIKE ‘表名’ 来查看当前表使用的行格式,其中row_format属性表示当前所使用的行记录结构类型。
Compact行记录的设计目标是高效地存储数据。简单来说,一个页存放的行数据越多,其性能就越高。Compact行记录的存储方式如下:
从图中可以看出:
-
Compact行记录格式的首部是一个非NULL变长字段长度列表,并且其是按照列的顺序逆序放置的,其长度为:
- 若列的长度小于255字节,用1字节表示;
- 若列长度大于255个字节,用2字节表示。
变长字段的长度最大不可以超过2字节,这是因为在mysql数据库中VARCHAR类型的最大长度限制为65535。
-
NULL标志位,指示了该行数据中是否有NULL值,有则用1表示;
-
记录头信息,固定占用5字节(40位),每位的含义如下:
-
最后的部分就是实际存储每个列的数据。
注:
- NULL不占该部分任何空间,即除了占有NULL标志位,实际存储不占有任何空间
- 每个数据行除了用户定义的列外。还有两个隐藏列,事务ID列和回滚指针列,分别为6字节和7字节大小
- InnoDB 表没有定义主键,每行还会增加一个6字节的rowid列
接下来会用一个具体的示例来分析Compact行记录的内部结构:
CREATE TABLE `mytest` (
`t1` varchar(10) DEFAULT NULL,
`t2` varchar(10) DEFAULT NULL,
`t3` char(10) DEFAULT NULL,
`t4` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT;
插入三条数据:
mysql> select * from mytest;
+------+------+------+------+
| t1 | t2 | t3 | t4 |
+------+------+------+------+
| a | bb | bb | ccc |
| d | ee | ee | fff |
| d | NULL | NULL | fff |
+------+------+------+------+
打开表空间文件mytest.ibd,内容如下:
该行记录从0000c078开始(第一行)
03 02 01 /*变长字段长度列表,逆序*/
00 /*NULL标志位,第一行没有NULL值*/
00 00 10 00 2C/*Record Header,固定5字节长度*/
00 00 00 2B 68 00/*RowID InnoDB自动创建,6字节*/
00 00 00 00 06 05/*TransactionID*/
80 00 00 00 32 01 10/*Roll Pointer*/
61/*列1数据 'a' */
62 62/*列2数据 'bb' */
62 62 20 20 20 20 20 20 20 20/*列3数据 'bb' */
63 63 63/*列4数据 'ccc' */
首先变长字段是逆序的,因此变长字段长度列表为 03 02 01,而不是01 02 03。可以看到,固定长度CHAR字段在未能完全占用其长度空间时,会用0x20来进行填充。
Rocord Header的最后两个字节next_recorder,0x2c代表下一个记录的偏移量,即当前记录的位置加上偏移量0x2c就是下一条记录的起始位置。所以InnoDB引擎在页内部是通过一种链表的结构来串联各个行记录的。
有NULL值得第三行:
03 01 /*变长字段长度列表,逆序*/
06 /*NULL标志位,第一行没有NULL值*/
00 00 20 ff 98/*Record Header,固定5字节长度*/
00 00 00 2b 68 00/*RowID InnoDB自动创建,6字节*/
00 00 00 00 06 07/*TransactionID*/
80 00 00 00 32 01 10/*Roll Pointer*/
64/*列1数据 'a' */
66 66 66/*列4数据 'fff' */
第三行有NULL值,因此NULL表示为不再是00 而是06,转成二进制位00000110,为1的值代表第2列和第3列的数据为NULL。可以发现,第三行只存储了第1列和第4列非NULL的值。
与Redundant行记录格式相比:
不管是CHAR类型还是VARCHAR类型,在Compact格式下NULL值都是不占用任何存储空间的。
对于VARCHAR类型的NULL值,Redudant行记录格式同样不会占用任何存储空间,而CHAR类型的NULL值需要占用空间。