innodb行格式(记录格式)
本文主要介绍了Compact行格式,其它的格式与这个格式很类似,只有一些小功能上的区别
查看innodb的行格式:innodb
MySQL8.0中默认的行格式为dynamic
select @@innodb_default_row-format
查看表的行格式:
show table status like 'tbl_dept'\G;
创建或者修改表的语句中指定行格式:
create table 表名(列的信息)ROW_FORMAT=行格式名称
alter table 表名 ROW_FORMAT=行格式名称
COMPACT行格式
一条完整的记录其实可以分为记录的额外信息和记录的真实数据两大部分。
变长字段长度列表
变长字段长度列表:记录一些可变长的列表(varchar类型定义的)的真实存储数据长度。
- 注意点:底层存储的变长长度和字段的顺序是反过来的
-
上表中对应的存储效果为(三个列都是varchar类型):
NULL值列表
NULL值列表:Compact格式会把所有可以为NULL的列统一管理起来,存在一个NULL值列表,如果表中没有允许为NULL的列,则NULL值列表也不复存在了。
为什么要有NULL值列表?
之所以要存储NULL是因为数据都是需要对齐的,如果没有标注出来NULL值的位置,就有可能在查询数据的时候出现混乱。如果使用一个特定的符号放到相应的数据位表示空置的话,虽然能达到效果,但是这样很浪费空间,所以直接就在行数据的头部开辟出一块空间专门用来记录该行数据哪些是非空数据,哪些是空数据,格式如下:
- 二进制位的值为1时,代表该列的值为NULL
- 二进制位的值为0时,代表该列的值不为NULL
记录头信息
记录头信息:
总大小为五个字节,各个属性信息如下:
名称 | 大小(单位: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 | 表示下一条记录的相对位置(通过链表的形式将用户记录链接起来) |
插入5条数据实例:
next_record:表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量
- 比如:第一条记录的next_ record值为32, 意味着从第一条记录的真实数据的地址处向后找32个字节便是下一条记录的真实数据。
- 注意,下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定Infimum记录(也就是最小记录)的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是Supremum记录(也就是最大记录)。下图用箭头代替偏移量表示next_ record。
疑问:被删除的记录为什么还在页中存储?
- 你以为它删除了,可它还在真实的磁盘上。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后其他的记录在磁盘上需要重新排列, 导致性能消耗。所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖
疑问:怎么不见heap_no值为0和1的记录呢?
- MySQL会自动给每个页里加了两个记录,由于这两个记录并不是我们自已插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录,一个代表最大记录最小记录和最大记录的heap_ no值分别是0和1,也就是说它们的位置最靠前。
真实数据
真实数据:
真实数据除了我们自定义的数据以外,还存在三个隐藏列(用于MVCC、回滚等等)
- row_id:一个表没有手动定义主键,则会选取一个Unique键作为主键, 如果连Unique键都没有定义的话,则会为表默认添加一个名为row_ id的隐藏列作为主键。所以row_ id是在没有自定义主键以及Unique键的情况下才会存在的。
- 事务ID和回滚指针是用于Mysql实现回滚指针和MVCC的
Dynamic和Compressed行格式
Dynamic和Compressed行格式整体上和Compact行格式相似,只不过在处理行溢出数据时有分歧
Compressed行记录格式的另-一个功能就是,存储在其中的行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARQHAR这类大长度类型的数据能够进行非常有效的存储。
行溢出:InnoDB存储引擎可以将一条记录中的某些数据存储在真正的数据页面之外。
一个VARCHAR类型的字段最多能够存储65533个字节
CREATE TABLE test_demo1(
c VARCHAR(65535) //失败
)
CREATE TABLE test_demo1(
c VARCHAR(65532) //成功 需要两个字节记录变长字段的长度 + 一个字节的NULL值的标识
)
CREATE TABLE test_demo1(
c VARCHAR not null(65533) //成功 需要两个字节记录变长字段的长度
)
由上述案例可知,一个数据页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65533个字节,这样就可能出现一个页存放不了一条记录,这种现象称为行溢出。
如何解决行溢出?
-
页的扩展:在Compact和Reduntant行格式中, 对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中进行分页存储,然后记录的真实数据处用20个字节存储指向这些页的地址(当然这20个字节中还包括这些分散在其他页面中的数据的占用的字节数),从而可以找到剩余数据所在的页。
Dynamic和Compressed行格式与Compact行格式在处理行溢出时的区别:
-
Compressed和Dynamic两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式。如图,在数据页中只存放20个字节的指针(溢出页的地址),实际的数据都存放在Off Page (溢出页)中。
-
Compact和Redundant两种格式会在记录的真实数据处存储一-部分数据(存放768个前缀字节)。
Redundant行格式
Redundant是MySQL 5.0版本之前InnoDB的行记录存储方式,MySQL 5.0支持Redundant是为了兼容之前版本的页格式。
Redundant行格式与Compact行格式主要区别:
- Redundant拥有记录所有字段长度的 字段长度偏移列表
- Redundant的记录头信息多了n_fields和1byte_offs_flag这两个属性,没有record_type这个属性
不同于Compact行格式,Redundant行格式的首部是一个字段长度偏移列表,同样是是按照列的顺序逆序放置的
字段长度偏移列表和之前的变长字段长度列表的区别:
- 少了“变长”两个字:Redundant行格式会把该条记录中==所有列(包括隐藏列)==的长度信息都按照逆序存储到字段长度偏移列表。
- 多了“偏移”两个字:这意味着计算列值长度的方式不像Compact行格式那么直观它是采用两个相邻数值的差值来计算各个列值的长度。
举例:比如第一条记录的字段长度偏移列表就是:
2B 25 1F 1B 13 0C 06
因为它是逆序排放的,所以按照列的顺序排列就是:
06 0C 13 17 1A 24 25
按照两个相邻数值的差值来计算各个列值的长度的意思就是:
- 第一列(row_ id)的长度就是0x06个字节,也就是6个字节;
- 第二列(transaction_ id)的长度就是(0x0C - 0x06)个字节,也就是6个字节;
- 第三列(roll_ pointer)的长度就是 (0x13 - 0x0C)个字节,也就是7个字节;
- 第四列(col1)的长度就是(0x1B - 0x13)个字节,也就是8个字节;
- 第五列(co|2)的长度就是(0x1F - 0x1B)个字节,也就是4个字节;
- 第六列(col3)的长度就是(0x25 - 0x1F)个字节,也就是6个字节;
- 第七列(col4)的长度就是(0x2B - 0x25)个字节,也就是6个字节。
记录头信息与Compact行格式的区别:
-
Redundant行格式中的记录头信息固定占用6个字节(48位),含义如下标所示
名称 大小(单位:bit) 描述 预留位1 1 没有使用 预留位2 1 没有使用 delete_mask 1 标记该记录是否被删除 min_rec_mask 1 B+树的每层非叶子节点中的最小记录都会添加该标记 n_owned 4 表示当前记录拥有的记录数 heap_no 13 表示当前记录在记录堆中的位置信息 n_fields 10 记录列中的数量 1byte_offs_flag 1 记录字段长度偏移列表中每个列对应的偏移量使用1个字节还是2个字节表示 next_record 16 表示下一条记录的相对位置(通过链表的形式将用户记录链接起来) -
Redundant行格式多了n_fields和1byte_offs_flag这两个属性
-
Redundant行格式中没有record_type这个属性