4、InnoDB记录结构

4、InnoDB记录结构


声明:此博客是本人学习《MySQL是怎样运行的:从根儿上理解MySQL》的学习笔记,侵权必删。

注:由于内容是由markdown文件直接导入的,难免会有错误。如果你发现博客中有错误,还请告诉我,我会第一时间修改并给予您诚挚的感谢ღ( ´・ᴗ・` )。

InnoDBInnoDB

  • InnoDB是存储引擎的一种,其将数据存储在磁盘中;
  • InnoDB页是在InnoDB存储引擎中磁盘和内存交互的最小单元,大小为16KB,类似于操作系统中的页。

InnoDB行格式

我们平时是以记录为单位来向表中插入数据的,这些数据在磁盘的存储方式就被称为"行格式"或者"记录格式"。

InnoDB存储引擎中,行格式有四种:CompactRedundantDynamicCompressed。那么如何指定行格式呢?

我们可以在创建表或修改表结构时指定行格式:

# 创建表时
CREATE TABLE 表名 (列的信息...) ROW_FORMAT=行格式名称;

# 修改表时
ALTER TABLE 表名 ROW_FORMAT=行格式名称;

# 例子:创建一个test测试表,指定它的行格式为 Compact,设置字符集为 ASCII
CREATE TABLE test (
	c1 VARCHAR(10),
    c2 VARCHAR(10) NOT NULL,
    c3 CHAR(10),
    c4 VARCHAR(10)
) CHARSET=ASCII ROW_FORMAT=COMPACT;

Compact格式

在这里插入图片描述

可以看到Compact格式的行格式包括额外信息和真实数据两部分,假设我们执行了如下语句:

INSERT INTO test (c1, c2, c3, c4)
VALUES ('aaaa', 'bbb', 'cc', 'd'),
	   ('eeee', 'fff', NULL, NULL);

下面我们对各个字段进行具体分析:

变长字段长度列表
基本概念:

将真实数据中所有可变长类型的字段所占用的字节长度记录下来,逆序排列形成一个列表放在行的头部。

为什么需要该列表

因为MySQL服务器需要知道每个字段的长度,但是可变长类型字段的长度是未知的,所以需要把这些字段的长度保存下来。

例子:

test表的第一行数据中的可变长字段列表:

字段   长度  16进制
aaaa   4    0x04
bbb    3    0x03
d      1    0x01

所以对于第一行数据,其Compact行格式中存储的变长字段长度列表为:010304,在整个行格式中可以表示为:

在这里插入图片描述

字节数判断

上面只用一个字节就表示可变长字段长度,因为字段实在太小,但我们实际开发中可能会遇到长度很大的字段,这时候一个字节就不够用了。对于何时用一个字节、何时用两个字节,InnoDB有自己的一套规则:

下面先声明一下规则中的W、M、L分别是什么意思:

W:当前字符集一个字符能占用的最大字节数。例如utf8中是3(之前说过MySQLutf8是指utf8mb3),gbk中是2,ASCII是1等;

M:当前可变长字段的类型允许的最大字符数。例如varchar(10)表示varchar这种类型只允许最多10个字符;

L:可变长字段占用的的实际字节数。例如"aaaa"字段占用的实际字节数是4,因为其可以用ASCII字符表示,而ASCII一个字符用一个字节表示;

具体规则如下:

  • W * M <= 255时,用一个字节表示;
  • W * M > 255,且L > 127时,用两个字节表示;
  • W * M > 255,且L < 127时,用一个字节表示。

针对上述规则,你可能有如下疑问:

  1. 为什么W * M是和255比较,而L和127比较呢?

前者是因为一个字节有8位,2^8就是256,即表示0-255;

后者是因为在InnoDB中,可变长字段的每个字节中第一个二进制位是标志位,不作为具体数据存储位置;

标志位作用:区分当前阅读的字节是一个完整的字段长度还是半个字段长度(如果字段是两个字节表示的,当前阅读字节就是半个字段长度)。
当标志位等于0时,表示是一个完整的字段长度,等于1表示是半个字段长度。

注意:

变长字段长度列表内只存储满足以下全部条件的字段:

  • 字段是可变长类型
  • 字段非空
NULL值列表
基本概念:

是用来存储记录中的所有NULL值字段的一个列表,用一个二进制位表示是否为NULL,值等于1表示该字段为NULL,同样也是逆序排列。

为什么需要该列表

该列表可以用二进制位来表示字段是否为NULL,这样可以减少空间的消耗。如果没有该列表,记录中的NULL值就只能存储在真实数据的存储区,而NULL值有时也是需要空间的,这样会导致空间浪费。

当NULL值所在列的类型为定长类型时(如"CHAR()“类型),其就会占用定长的内存;当NULL值所在列类型为变长类型时(如"VARCHAR”),则不占用任何内存。

例子:

test表中的第二行记录如下:

字段  值
c1   eeee
c2   fff
c3   NULL
c4   NULL

其中c3、c4字段为NULL,所以这两位二进制位应该为1,其余为0,表示为:

在这里插入图片描述

因为字段c2被声明为了NOT NULL,所以其不会被添加在NULL值列表内。

注意:
  • 可以没有NULL值列表:如果记录中全部字段均不为NULL,或者全部字段均设置了NOT NULL约束,那么NULL值列表就不存在;
  • NULL值列表要求必须用整字节来表示NULL值:如果记录中NULL值数小于1个字节,就用0补齐高位,如果大于1个字节,小于2个字节,就用两个字节表示,第二个字节用0补齐高位。
记录头信息
基本信息

记录行性质的相关信息,如行是否有效、是否已被删除、行在堆中的位置、下一条记录的相对位置等等。

组成结构

由固定的五个字节组成,不同位有不同位的功能,其中高位的前两个二进制位是预留位。

具体位与功能图如下:

在这里插入图片描述

名称大小(bit)功能
预留位1、2各1bit 未使用
delete_mask1标记该记录是否被删除
min_rec_mask1B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned4表示当前记录拥有的记录数
heap_no13表示当前记录在记录堆的位置信息
record_type3表示当前记录的类型,0 表示普通记录,1 表示B+树非叶子节点记录,2 表示最小记录,3表示最大记录
next_record16表示下一条记录的相对位置
记录的真实数据
除了我们自己定义的列,MySQL还会有选择的添加几个新的列。新列如下表:
列名是否必须占用空间描述
DB_ROW_ID6 字节行 ID,唯一标识一条记录
DB_TRX_ID6 字节事务 ID
DB_ROLL_PTR7 字节回滚指针
InnoDB 表选取主键的策略:
  1. 若有自定义主键,优先选取自定义的主键;
  2. 若没有自定义主键,则选取一个带有Unique约束的列作为主键;
  3. 若自定义主键、Unique约束都没有,则会添加新的列DB_ROW_ID作为主键。

记住,MySQL会为每一行记录添加列DB_TRX_ID和列DB_ROLL_PTR。而只有当表中自定义主键和Unique约束都没有时,MySQL才会添加列DB_ROW_ID作为主键。

CHAR(M)的特殊格式
CHAR(M)的含义:

最多能包含 M 个字符,但少于 M 个字符时,其所占空间与包含 M 个字符相同。

优点:

更容易将空间对齐,不容易产生空间碎片。

特殊格式:对于不同的字符集,在"可变长字段列表"中有不同的行为。
  • 当所属字符集是单字节字符集(最大字符长度为1个字节)时,CHAR(M)不会被添加到"可变长字段列表"中;
  • 当所属字符集是多字节字符集时,CHAR(M)会被添加到"可变长字段列表"中。

Redundant行格式

这是一种很古老的一种行格式,被使用在MySQL5.0时代,所以现在看来是有点过时的。有意思的是,这种行格式的名字Redundant本身就有"淘汰、累赘"的意思(手动doge)。

这种行格式的示意图如下:

在这里插入图片描述

下面具体来说说各个字段的含义:

字段长度偏移列表

相较于Compact行格式,这种行格式没有了"可变长字段长度列表",而添加了个"字段长度偏移列表"。

基本概念

记录所有列在"记录真实数据区"的偏移字节量,同样,也是逆序的。

需要记住两个点:

  • 记录所有列的偏移
  • 逆序排列偏移量
记录头信息

大小为 6 个字节。相较于Compact行格式,这种行格式多了n_field1byte_offs_flag属性,少了record_type属性。下面对多了的属性进行解释:

名称大小(bit)描述
n_field10该行记录中包含的列数
1byte_offs_flag1表示每个列的偏移量是一个字节表示还是两个字节表示的。1表示1个字节,0表示2个字节
CHAR(M)的存储方式

Redundant行格式下的CHAR(M)不用分情况讨论,只有一个存储方式:字符集的最大表示字节数 × M。如GBK的最大表示字节数位2,假设M=10,那么GBK下的CHAR(10)就占20个字节。

DynamicCompressed行格式

这两个行格式与Compact行格式很像,只不过在处理行溢出时的行为不太一样,这两个行格式不会在记录页存储数据,而是把溢出的数据全都放在溢出页,在记录页只存放溢出页的地址。

行溢出

当一个行中存储了很大的数据时,可能会发生行溢出。

CompactRedundant行格式下发生行溢出之后,当前页中存储格式为:部分数据+溢出页地址。

DynamicCompressed行格式下发生行溢出,当前页中只存储溢出页地址。

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值