认真学习InnoDB的行格式

我们平时的数据以行为单位来向表中插入数据,这些记录在磁盘上的存放方式也被称为行格式或者记录格式。InnoDB存储引擎设计了4中不同类型的行格式,分别是Compact、Redundant、Dynamic和Compressed行格式。

查看MySQL默认行格式:

# 查看变量
show VARIABLES like '%innodb_default_row_format%'

# 查看MySQL系统变量
select @@innodb_default_row_format

也可以通过查看表结构信息来判断具体表的行格式:

show table status like 'tb_sys_file'

在这里插入图片描述
我们可以在创建或修改表的语句中指定行格式。

create table 表名(列的信息) row_format=行格式名称
alter table 表名 row_format=行格式名称

接下来我们详细学习四种行格式。

【1】Compact行格式

在MySQL5.1版本中,默认设置为Compact行格式。一条完整的记录其实可以被分为记录的额外信息和记录的真实数据两大部分。
在这里插入图片描述

① 变长字段长度列表

MySQL支持一些变长的数据类型,比如VARCHAR(M)、 VARBINARY(M)、 TEXT类型、BLOB类型,这些数据类型修饰列称为变长字段。变长字段中存储多少字节的数据不是固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存起来。

在Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表。

需要注意的是,这里面存储的变长长度和字段顺序是相反的。比如两个varchar字段在表结构的顺序是a(10),b(15),那么在变长字段长度列表中存储的长度顺序就是15,10,是反过来的。

② NULL值列表

Compact行格式会把可以为null的列统一管理起来,存在一个标记为NULL值列表中。如果表中没有允许存储NULL的列,则null值列表也不存在了。

为什么定义Null值列表?

之所以要存储null是因为数据都是需要对齐的,如果没有标注出来null值的位置,就有可能再查下数据的时候出现混乱。如果使用一个特定的符号放到相应的数据位表示空值的话,虽然都能达到效果,但是这样很浪费空间,所以直接就在行数据的头部开辟出一块空间专门用来记录该行数据哪些是非空数据,哪些是空数据。

格式如下:

  • 二进制位的值为1时,代表该列的值为null;
  • 二进制位的值为0时,代表该列的值不为null。

例如字段a 、b、 c ,其中a是主键,在某一行中存储的数据依次是 a=1,b=null,c=2 ,那么Compact行格式中的null值列表中存储:01(仍旧为倒序哦) 。第一个 0 表示 c 不为null,第二个1 表示 b 是null。这里之所以没有a是因为数据库会自动跳过主键,因为主键肯定是非null且唯一的,在null值列表的数据中就会自动跳过主键。

③ 记录头信息(5字节)

记录头信息(5字节):

  • delete_mask
  • min_rec_mask
  • n_owned
  • heap_no
  • record_type
  • next_record

假设表DDL如下所示,那么此时行记录与记录头格式参考下图。

create table page_demo(
	c1 int,
	c2 int,
	c3 varchar(10000),
	primary key (c1)
)charset=ascii row_format=compact;

这部分应该在InnoDB的行格式一章中,这里是为了说明用户记录形成单链表。

在这里插入图片描述
这些记录头信息中各个属性如下:

名称大小(bit)描述
预留位11没有使用
预留位21没有使用
delete_mask1标记该记录是否被删除。0 - 没有被删除,1 - 记录被删除
min_rec_mask1B+树的每层非叶子节点中的最小记录都会添加该标记,值为1。 0 - 表示不是B+树的非叶子节点中的最小记录
n_owned4表示当前记录拥有的记录数 。页目录中每个组最后一条记录的头信息中会存储该组一共有多少条记录,作为n_owned字段
heap_no13表示当前记录在本页中的位置信息
record_type3表示当前记录的类型:0 - 普通记录, 1- B+树非叶子节点记录,2 - 最小记录,3 - 最大记录
next_record16表示下一条记录的相对位置,也就是从当前记录的真实数据到下一条记录的真实数据的地址偏移量。

注意,next_record中下一条记录指的并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定Infimum记录(也就是最小记录)的下一条记录就是本页中主键值最小的用户记录。而本页中主键值最大的用户记录的下一条记录就是Supremum记录(也就是最大记录)。

关于delete_mask有个疑问:被删除的记录为什么还在页中存储呢?

实际上记录并没有从磁盘消失。这些被删除的记录之所以不立即从磁盘上移除是因为移除它们之后其他的记录在磁盘上需要重新排列,导致性能消耗。所以只是打一个标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为可重用空间,之后如果有新纪录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

heap_no是没有值为0和1的

这是因为MySQL会自动给每个页里加两个记录,这两个记录并不是我们自己插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录,一个代表最大记录。最小记录和最大记录的heap_no值分别是0和1,也就是说它们的位置最靠前。

④ 记录的真实数据

记录的真实数据除了我们自己定义的列的数据以外,还会有三个隐藏列。

列名是否必填占用空间描述
row_id6字节行ID,唯一标识一条记录
transaction_id6字节事务ID
roll_pointer7字节回滚指针

实际上这几个列的真正名称其实是:DB_ROW_ID、 DB_TRX_ID 、DB_ROLL_PTR。

一个表没有手动定义主键,则会选取一个Unique键作为主键。如果连Unique键都没有定义的话,则会为表默认添加一个名为row_id的隐藏列作为主键。所以row_id是在没有自定义主键以及Unique键的情况下才会存在的。

【2】行溢出

相信大家对下面这个错误不陌生。报错表达的意思是:MySQL对一条记录占用的最大存储空间是有限制的,除BLOB或TEXt类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节。

[Err] 1118 - 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
在这里插入图片描述
以下表为例,如果列没有not null属性,那么最大为65532个字节。

CREATE TABLE `page_demo2` (
  `c3` varchar(65532) 
) ENGINE=InnoDB DEFAULT CHARSET=ascii

如果有not null属性,那么就不需要NULL值标识,也就可以多存储一个字节,即65533个字节。

CREATE TABLE `page_demo2` (
  `c3` varchar(65533) not null 
) ENGINE=InnoDB DEFAULT CHARSET=ascii

即对上表而言: 65535 - 2字节变长长度 - 1字节null标识 = 变长列占用空间 。

通过上面的案例,我们可以知道一个页的大小一般是16KB ,也就是16384字节,而一个VARCHAT(M)类型的列最多可以存储65533个字节,这样就可能出现一个页存放不了一条记录,这种现象称为行溢出。

在Compact 和 Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中进行分页存储。然后记录的真实数据处用20个字节存储指向这些页的地址(当然这20个字节中还包括这些分散在其他页面中的数据的占用的字节数),从而可以找到剩余数据所在的页,这称为页的扩展。
在这里插入图片描述

【3】Dynamic 和 Compressed行格式

在MySQL8.0中,默认行格式就是Dynamic。Dynamic、Compressed行格式和Compact行格式挺像,只不过在处理行溢出数据时有分歧:

  • Compressed和Dynamic两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式。如图,在数据页中只存放20个字节的指针(溢出页的地址),实际的数据都存放在Off Page(溢出页)中。
  • Compact 和 Redundant两种格式会在记录的真实数据处存储一部分数据(存放768个前缀字节)。
    在这里插入图片描述

Compressed行记录格式的另一个功能就是,存储在其中的行数据会以zlib的算法进行压缩,因此对于blob、text、varchar这类大长度类型的数据能够进行非常有效的存储。

【4】Redundant行格式

Redundant是MySQL5.0 版本之前InnoDB的行记录存储方式,MySQL5.0支持Redundant是为了兼容之前版本的页格式。

在这里插入图片描述
从上图可以看到,不同于Compact行记录格式,Redundant行格式的首部是一个字段长度偏移列表,同样是按照列的顺序逆序放置的。

注意Compact行格式的开头是变长字段长度列表,而Redunant行格式的开头是字段长度偏移列表,与变长字段长度列表有两处不同:

  • 少了“变长”两个字:Redundant行格式会把该条记录中所有列(包括隐藏列)的长度信息都按照逆序存储到字段长度偏移列表。
  • 多了“偏移”两个字:这意味着计算列值长度的方式不像Compact行格式那么直观,它是采用两个相邻数值的差值来计算各个列值的长度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值