innodb行记录
本文是对小册MySQL是怎样运行的:从根儿上理解MySQL中第五章的总结。关于一些细节可以购买小册查看。
以下大多数内容是基于compact行格式讲述,使用其它格式会有特别说明。
变长字段长度列表
MySQL建表的时候会指定字段类型,字段类型中包括变长字段,如:varchar(10)。假如存储的数据没有10个字符,这时候数据库不会占用磁盘10个字符的所需的空间,只会占用实际记录所需的空间+行记录额外信息。
建表时就会确定表字段的字符集,不同字符集每个字符所占用的字节数不同,如:utf8字符集中每个字符占用3个字节,gbk字符集中每个字符占用2个字节,ascii字符集中每个字符占用1个字节。
每一行记录存储的时候,都会存储一个列表,该列表的内容就是变长字段实际的长度列表,与行记录的顺序是相反的。
假如一个字段存储的内容,超过了256字节,便意味着变长字段长度列表中,该字段的数值超过了1字节,需要两个字节来表示。存储一个数值需要多少字节,检索的时候如何判断,在小册中有详细的解释,感兴趣的可以去看这里。
这样做的目的是,可以通过检索变长字段列表就去找到存储的数据。不过变长字段长度列表中只存储值为非NULL的列内容占用的长度,值为NULL的列会存储在null值列表中。所以需要结合null值列表才能准确的检索到某个字段的内容。
null值列表
没有被not null修饰过的字段,才可以出现在该列表中; 该列表的顺序和行记录的顺序是相反的(和变长字段列表规则一样); null用1表示,非null用0表示; 图文的详细说明看这里。
记录头信息
记录头由40个二进制位,不同的位代表不同的意思,具体信息如下表(表格来源 )
| 名称 | 大小(单位:bit) | 描述 |
|---|---|---|
| 预留位1 | 1 | 没有使用 |
| 预留位2 | 1 | 没有使用 |
| delete_mask | 1 | 标记该记录是否被删除 |
| min_rec_mask | 1 | B+树的每层非叶子节点中的最小记录都会添加该标记 |
| n_owned | 4 | 表示当前记录拥有的记录数 |
| heap_no | 13 | 表示当前记录在记录堆的位置信息 |
| record_type | 3 | 表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录 |
| next_record | 16 | 表示下一条记录的相对位置 |
每一行记录的真实数据中,除了以上的内容,还包括隐藏列,隐藏列如下表(表格来源 )
| 列名 | 是否必须 | 占用空间 | 描述 |
|---|---|---|---|
| DB_ROW_ID | 否 | 6字节 | 行ID,唯一标识一条记录 |
| DB_TRX_ID | 是 | 6字节 | 事务ID |
| DB_ROW_ID | 是 | 7字节 | 回滚指针 |
行溢出数据
行溢出原因:
在MySQL中,一行记录的内容有:变长字段长度列表,null值标识,真实数据。 一行记录最多可以存放65535字节数据,假设一张表只有一个字段,该字段允许null值。 则一行记录的最大分配方式为:存放65532字节真实数据。因为,null值列表占用1字节,变长字段列表占用2字节。如果存放更多的数据MySQL会报错。 在MySQL中一个页(page)的大小为16KB(即16384字节),所以会出现一行记录数据大小超过一页。MySQL通过行溢出来处理这种情况。
MySQL处理方式:
MySQL对于占用存储空间非常大的列,在
记录的真实数据处只会存储该列前768个字节 + 20个字节存储指向其它页的地址(这20个字节中还包括这些分散在其他页面中的数据的占用的字节数)。
关于768字节如何验证以及20个字节具体有些什么可以看这篇文章
MySQL中页数据的分配(具体怎么计算的,看这里 ):
每行记录的信息,除了真实数据之外,还有
27字节行记录额外数据。 每个页除了需要存放实际的数据之外,还需要132个字节的空间来存放额外信息。 MySQL规定一个页中至少存放两行记录。 所以,当某列记录大于一个临界点时,就会出现行溢出。
Dynamic和Compressed行格式
这两种行格式,在行溢出时,不会存储768字节真实数据,而是全部数据都存放在其它页中。Compressed还会用压缩算法压缩数据,顺便提一句,如果使用Compressed行格式,对cpu的消耗会多一些。