MySQL存储引擎InnoDB行格式

38次阅读
没有评论

共计 3255 个字符,预计需要花费 9 分钟才能阅读完成。

InnoDB 规划了 26 种行格式,分别对应 26 种动物,首字母由 A 至 Z:Antelope, Barracuda, Cheetah, Dragon, Elk, Fox, Gazelle, Hornet, Impala, Jaguar, Kangaroo, Leopard, Moose, Nautilus, Ocelot, Porpoise, Quail, Rabbit, Shark, Tiger, Urchin, Viper, Whale, Xenops, Yak, Zebra。

目前 InnoDB 支持的行格式只有 Antelope, Barracuda。而 Antelope 又具体细分为 Redundant 和 Compact,Barracuda 也具体细分为 Dynamic 和 Compressed。

可以在创建或修改表的语句中指定行格式:

CREATE TABLE 表名 (列的信息) ROW_FORMAT= 行格式名称
ALTER TABLE 表名 ROW_FORMAT= 行格式名称

Compact

一条完整的记录可以被分为 记录额外信息 记录真实数据 两大部分。

记录额外信息

记录额外信息是服务器为了描述这条记录而不得不额外添加的一些信息,这些额外信息分为 3 类,分别是 变长字段长度列表 NULL 值列表记录头信息

变长字段长度列表

在 Compact 行格式中,把所有变长字段的真实数据占用字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用字节数(十六进制表示)按照列的顺序 逆序 存放。

用 1 个还是 2 个字节来表示真实数据占用字节数,InnoDB 有它的一套规则:

  1. 假设某个字符集中表示一个字符最多需要使用的字节数为 W,也就是使用SHOW CHARSET 语句结果中的 Maxlen 列,比方说 utf8 字符集中 W 就是 3gbk 字符集中 W 就是 2ascii 字符集中 W 就是1
  2. 对于变长类型 VARCHAR(M) 来说,这种类型表示能存储最多 M 个字符(注意是字符不是字节),所以这个类型能表示的字符串最多占用的字节数就是M × W
  3. 假设实际存储的字符串占用字节数是L

所以确定使用 1 个字节还是 2 个字节表示真正字符串占用字节数规则就是这样:

  • 如果M×W <= 255,那么使用 1 个字节来表示真正字符串占用字节数
  • 如果M×W > 255,则分为两种情况:

    • 如果L <= 127,则用 1 个字节来表示真正字符串占用字节数
    • 如果L > 127,则用 2 个字节来表示真正字符串占用字节数

InnoDB 在读记录的变长字段长度列表时会先查看表结构,如果某个变长字段允许存储的最大字节数大于 255 时,该怎么区分它正在读的某个字节是一个单独的字段长度还是半个字段长度呢?InnoDB 使用该字节的第一个二进制位作为标志位:如果该字节的第一个位为 0,那该字节就是一个单独的字段长度(使用一个字节表示不大于 127 的二进制的第一个位都为 0),如果该字节的第一个位为 1,那该字节就是半个字段长度。

需要注意的是,变长字段长度列表中只存储值为 非 NULL的列内容占用长度,值为 NULL 的列长度是不储存的。

对于 CHAR(M) 类型的列来说,当列采用的是定长字符集时,该列占用字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用字节数也会被加到变长字段长度列表。

最后,并不是所有记录都有这个变长字段长度列表部分,比方说表中所有列都不是变长数据类型,这一部分就不需要有。

NULL 值列表

如果把 NULL 值都放到 记录真实数据 中存储会很占地方,所以 Compact 行格式把这些值为 NULL 的列统一管理起来,存储到 NULL 值列表中,它的处理过程是这样的:

  1. 首先统计表中允许存储 NULL 的列有哪些
  2. 如果表中没有允许存储 NULL 值的列,则 NULL 值列表也不存在,否则将每个允许存储 NULL 的列对应一个二进制位,二进制位按照列的顺序 逆序 排列,二进制位表示的意义:值为 1 时,代表该列的值为 NULL;值为0 时,代表该列的值不为NULL
  3. MySQL 规定 NULL 值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0。如果一个表中有 9 列允许为 NULL,那这个 NULL 值列表部分就需要 2 个字节来表示

记录头信息

由固定的 5 个字节组成,即 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 表示下一条记录的相对位置

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

heap_no这个属性表示当前记录在记录堆的位置信息,InnoDB 会自动给每个页里边儿加了两个记录,由于这两个记录并不是我们自己插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录(heap_no 值为 0),一个代表最大记录(heap_no 值为 1)。

next_record非常重要,它表示从当前记录真实数据到下一条记录真实数据的地址偏移量。比方说第一条记录的 next_record 值为 32,意味着从第一条记录真实数据的地址处向后找 32 个字节便是下一条记录真实数据。

记录真实数据

记录真实数据除了自己定义的列数据外,MySQL 会为每个记录默认添加一些列(也称为隐藏列),具体如下:

列名 是否必须 占用空间 描述
row_id 6 字节 行 ID,唯一标识一条记录
transaction_id 6 字节 事务 ID
roll_pointer 7 字节 回滚指针

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

需要注意的是,变⻓字符集的 CHAR(M)类型列要求⾄少占⽤ M 个字节,⽽ VARCHAR(M)却没有这个要求。

Redundant

该行格式是 MySQL 5.0 之前使用的一种行格式,也就是说它已经非常老了,这里就不介绍了。

行溢出

对于 VARCHAR(M)类型的列最多可以占用 65535 个字节,其实这个 65535 除了列本身的数据之外,还包括一些其他数据(storage overhead)。如果该 VARCHAR 类型列没有 NOT NULL 属性,那最多只能存储 65532 个字节数据,因为真实数据长度可能占用 2 个字节,NULL 值标识需要占用 1 个字节。

在 Compact 和 Reduntant 行格式中,对于占用存储空间非常大的列,在记录真实数据处只会存储该列的一部分数据,剩余数据分散存储在几个其他页中,然后记录真实数据处用 20 个字节存储指向这些页的地址,从而可以找到剩余数据所在的页。这个过程也叫做行溢出,存储剩余数据所在页也被称为溢出页。

发生行溢出的临界点是什么呢?也就是说在列存储多少字节数据时会发生 行溢出?其实,不用关注这个临界点是什么,只要知道如果一个行中存储了很大的数据时,可能发生行溢出的现象就行了。

Dynamic 和 Compressed 行格式

MySQL 5.7 默认行格式就是 Dynamic,这俩行格式和 Compact 行格式挺像,只不过在处理行溢出数据时有点儿分歧,它们不会在记录真实数据处存储字段真实数据的前 768 个字节,而是把所有字节都存储到其他页面中,只在记录真实数据处存储其他页面的地址。

Compressed 和 Dynamic 不同的一点是,Compressed 行格式会采用压缩算法对页面进行压缩,以节省空间。

正文完
post-qrcode
 0
三毛
版权声明:本站原创文章,由 三毛 于2024-01-29发表,共计3255字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)