mysql04——行格式
innoDB简介
读写磁盘到速度是非常慢的,和内存读写差了几个数量级,所以InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为16kb。也就是一般情况下,一次最少从过年磁盘中读取16kb的内容的内存中,一次最少把内存中的16kb内存刷新到磁盘中。
InnoDB行格式
InnoDB存储引擎设计了4种不同类型的行格式:
- Compact
- Redundant
- Dynamic
- Compressed
create table 表名 (列信息)ROW_FORMAT=行格式名称
alter table 表名 ROW_FORMAT=行格式名称
Compact
一条完整的记录被分为:额外信息和真实数据。服务器为了描述这条记录而不得不额外添加的一些信息,这些额外信息分为3类:
- 变长字段长度列表
- NULL值列表
- 记录头信息
变长字段长度列表
- 对于拥有VARCHAR(M)这样类型的列称为变长字段。在把存储真实数据的时候需要顺便把这些数据占用的字节数页存储起来,所以这些变长字段占用的存储空间分为两部分:真实的数据内容和占用的字节数列表
- 占用的字节数:Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而行程一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放。
- 这个地方的M是指M个字符,实际允许存储的最大字节数为M*4(如果是utf8mb4)。
- 如果内容占用字节数较少,就用一个字节表示变长字节长度,如果内容占用较多,就用两个字节表示。
- 2个字节大小就是16位,最大可以表示65535个字节。
- 变长字段长度列表中只存储值为非NULL的列内存占用的长度,值为NULL的列的长度是不存储的。
- 并不少所有记录都有这个变长字段长度列表部分,如果没有这个数据类型就不需要。
NULL值列表
Compact行格式把这些值为NULL的列统一管理起来,存储到NULL值列表中,处理过程如下:
- 首先统计表中允许存储NULL的列有哪些。
- 如果表中没有允许存储NULL的列,则NULL值列表也不存在列。否则将每个允许存储NULL的列对应一个二进制位,二进制位按照列的顺序逆序排列。
- 二进制位为1时,表示该列为NULL。
- 二进制位为0时,表示该列不为null。
- 比如某表中3个值允许为NULL,就对应3个二进制位,不足一个字节,所以所在字节的高位补0。如果一个表中需有9个允许为NULL,那么这个记录的NULL值列表部分就需要2个字节来表示了。
记录头信息
它由固定的5个字节也就是40个二进制位组成。
- 预留位1:1bit
- 预留位2:1bit
- delete_mask:1bit
- min_rec_mask:1bit。B+树的每层非叶子节点中的最小记录都会添加该标记。
- n_owned:4bit。当前记录拥有的记录数。
- heap_no:14bit。
- record_type:表示B+树飞叶子节点记录。
- next_record:16下一套记录的相对位置。
记录的真实数据
对于表来说,记录的真实数据除了几个自己定义的列的数据以外,Mysql会为每个记录默认的添加一些列(也称为隐藏列),具体的列如下:
列名 | 是否必须 | 占用空间 | 描述 |
---|---|---|---|
db_row_id | 否 | 6字节 | 行ID |
db_trx_id | 是 | 6字节 | 事务id |
db_roll_ptr | 是 | 7字节 | 回滚指针 |
- InnoDB表对逐渐对生成策略:优先是有用户自定义主键作为主键,如果用户没有定义主键,则选取一个unique键作为主键,如果没有unique也没有,则InnoDB会为表默认添加一个名为row_id对隐藏列作为主键。
- InnoDB存储引擎会为每一条记录都添加
db_trx_id
和db_roll_ptr
这两个列,但是row_id
是可选的。
CHAR(M)列的存储格式
- 对于CHAR(M)类型的列来说,当列采用的是定长字符集(比如ascii)时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集(比如utf8mb4)时,该列占用的字节数也会被驾到变长字段长度列表。
- 变长字符集的CHAR(M)类型的列要求至少占用M个字节,而VARCHAR(M)却没有这个要求。比如utf8mb4的char(10)的列占用字节范围是10~40。(空字符串也是10个字节)
Redundant行格式
MySQL5.0之前的格式,略。
行溢出数据
VARCHAR(M)最多能存储的数据
对于VARCHAR(M)的列最多占用65535个字节。实验:
create table varchar_size_demo(
c varchar(65535)
)charset=ascii row_format=compact;
结论:
- 除了BLOB和TEXT类型的列,其他的所有列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节。
- 如果VARCHAR()有
NOT NULL
属性,那么真实数据可能占用2个字节,不需要null值标识,所以最大可以存储65533个字节。对于utf8mb4最多存储16383个字符。
记录中的数据太多产生的溢出
Mysql中磁盘和内存交换的基本单位是页,而一个页的大小一般是16kb,也就是16384字节,而一个VARCHRA(M)类型的列就最多可以存储65532个字节,这样就可能造成一个页存放不列一条记录的尴尬情况。
在Compact和Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址。
[...][字符串的前768字节][溢出页地址][...]
除了VARCHAR(M)类型的列,其他的text和blob类型的列在存储数据非常多的时候也会发生行溢出。
行溢出的临界点
Mysql中规定一个页中至少存放两条记录,否则就会发生行溢出。如果一个表中只有一列,在页空间中:
-
额外信息需要136个字节的空间
-
每条记录需要额外信息是27字节,2个存储真实数据长度,1个NULL,5个头信息,6个rowid,6个trxid,7个rollptr。
所以填满需要:136 + 2 * (27 + n) > 16kb n > 8098
所以,如果一个行中存储了很大的数据时,可能发生行溢出的现象。
Dynamic和Compressed行格式
Mysql5.7和Mysql8以上都默认都使用Dynamic,这两个行格式类型和Compact很像,只不过在处理行溢出数据时有点分歧。它们不会在记录都真实数据处存储字段真实数据都前768个字节,而是把所有都字节都存储到其他页面中,只在记录到真实数据处存储其他页面的地址。
Compressed行格式和Dynamic不同的一点是,会压缩算法对页面进行压缩。