InnoDB从磁盘中读取数据的最小单位是数据页。而id = xxx的数据,就是这个数据页众多行中的一行。
一、行格式
1.1 有哪些行格式?
MySQL的数据行有两种格式:一种是Compact格式,还有一种是Redundant格式。
Compact是一种紧凑的行格式,设计的初衷就是为了让一个数据页中可以存放更多的数据行。Compact能比Redundant格式节约20%的存储。
1.2 Compact行格式长啥样?
表中有的列允许为null,有的列是变长的varchar类型。那Compact行格式是如何组织描述这些信息的呢?如下图:
二、行大小
2.1 单行能存多大体量的数据?
在MySQL的设定中,单行数据最大能存储65535byte的数据(注意是byte,而不是字符)。但是当你像下面这样创建一张数据表时却发生了错误:
mysql> create table `t0`(-> `a` varchar(65535) NOT NULL-> )ENGINE=InnoDB DEFAULT CHARSET=latin1;ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs
MySQL不允许创建一个长度为65535byte的列,因为数据页中每一行中都有我们上图提到的隐藏列。
所以将varchar的长度降低到65532byte即可成功创建该表
mysql> create table `t0`( `a` varchar(65532) NOT NULL )ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.03 sec)
注意这里的65535指的是字节,而不是字符。
所以如果你将charset换成utf8这种编码格式,那varchar(N)中的N其实指的N个字符,而不是N个byte。所以如果你像下面这样创建表就会报错。
mysql> create table `t00`( `a` varchar(65532) NOT NULL )ENGINE=InnoDB DEFAULT CHARSET=utf8;
ERROR 1074 (42000): Column length too big for column 'a' (max = 21845); use BLOB or TEXT instead
假如encode=utf8时三个byte表示一个字符。那么65535 / 3 = 21845个字符。
2.2 Compact格式是如何做到紧凑的?
MySQL每次进行随机的IO读,默认情况下,数据页的大小为16KB。数据页中存储着数行。那就意味着一个数据页中能存储越多的数据行,MySQL整体的进行的IO次数就越少?性能就越快?
Compact格式的实现思路是:当列的类型为VARCHAR、 VARBINARY、 BLOB、TEXT时,该列超过768byte的数据放到其他数据页中去。如下图:
MySQL这样做,有效的防止了单个varchar列或者Text列太大导致单个数据页中存放的行记录过少而让IO飙升的窘境且占内存的。
三、行溢出
3.1 什么是行溢出?
如果数据页默认大小为16KB,换算成byte: 16*1024 = 16384 byte。那单页能存储的16384 byte和单行最大能存储的 65535 byte 差了好几倍呢?也就是说,假如你要存储的数据行很大超过了65532 byte那么你是写入不进去的。假如你要存储的单行数据小于65535 byte但是大于16384 byte,这时你可以成功插入,但是一个数据页又存储不了你插入的数据。这时肯定会行溢出!
其实在MySQL的设定中,发生行溢出并不是达到16384 byte边缘才会发生。对于varchar、text等类型的行。当这种列存储的长度达到几百byte时就会发生行溢。
3.2 行如何溢出?
还是看这张图:
在MySQL设定中,当varchar列长度达到768byte后,会将该列的前768byte当作当作prefix存放在行中,多出来的数据溢出存放到溢出页中,然后通过一个偏移量指针将两者关联起来,这就是行溢出机制。
四、思考一个问题
不知道你有没有想过这样一个问题:首先你肯定知道,MySQL使用的是B+Tree的聚簇索引,在这棵B+Tree中非叶子节点是只存索引不存数据,叶子节点中存储着真实的数据。同时叶子结点指向数据页。那当单行存不下的时候,为啥不存储在两个数据页中呢?就像下图这样~。单个节点存储下,我用多个节点存总行吧!说不定这样我的B+Tee还能变大长高(这其实是错误的想法)
这个错误的描述对应的脑图如下:
MySQL不这样做的原因如下:MySQL想让一个数据页中能存放更多的数据行,至少也得要存放两行数据。否则就失去了B+Tree的意义。B+Tree也退化成一个低效的链表。
每个数据页至少要存放两行数据的意思不是说数据页不能只存一行。这句话的意思是,当你往这个数据页中写入一行数据时,即使它很大将达到了数据页的极限,但是通过行溢出机制,依然能保证你的下一条数据还能写入到这个数据页中。
正确的脑图如下:
