目 录CONTENT

文章目录

InnoDB的记录结构

FatFish1
2025-04-28 / 0 评论 / 0 点赞 / 26 阅读 / 0 字 / 正在检测是否收录...

InnoDB基本概念

众所周知,MySQL是有很多种不同的存储引擎,还记得存储引擎=数据存储格式+提取操作

数据在不同存储引擎中存放的格式是不同的,甚至存储介质都不同,比如只用内存存储的Memory引擎,关机后数据就消失

InnoDB是最常用的存储引擎,它的特点有:

  • 使用磁盘做为存储介质,关机重启后数据还存在

  • 将数据划分为若干个16KB的页,做为磁盘和内存的交互基本单位,因此每次读取和写入都是16KB的倍数

InnoDB行格式

InnoDB行格式的分类

数据是以数据行的形式存储在数据库中的,增删改查都是针对的数据行,因此就要看下InnoDB存储数据的行格式是啥样的

InnoDB的行格式分类有:Compact、Redundant、Dynamic、Compressed,使用以下案例分析各种行格式:

行格式可以用ROW_FORMAT语法设置:

CREATE TABLE record_format_demo (
	c1 VARCHAR(10),
	c2 VARCHAR(10) NOT NULL,
	c3 CHAR(10),
	c4 VARCHAR(10)
) CHARSET=ascii ROW_FORMAT=COMPACT;

插入两条数据:

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

Compact行格式

Compact行格式的具体格式如下:

变长字段长度列表

NULL值记录表

记录头信息

真实记录信息

变长字段长度列表

存储的是真实记录中变长字段中非NULL值的长度,逆序存储

这就要提下VARCHAR和CHAR的区别了,VARCHAR(255)类型表示最长255个字符,但是实际存储在库里面,如果存不到255字符,实际不会分配255个字符需要的字节数

例如在ascii字符集,一个字符需要1个字节存储,ascii字符集下使用VARCHAR(255),存255个字符就分配255个字节,存1个字符就分配1个字节

而CHAR类型是定长类型,ascii字符集下使用CAHR(255),存255个字符就分配255个字节,存1个字符还是分配255个字节,因此使用VARCHAR可以节省空间

那么在这种情况下,变长字段长度列表是怎么存的?

这里涉及到变长字段所能容纳的最大字节数:

  • 变长字段的最大字节数 x 每个字节所占字符数 <= 255时,使用一个字节存储其长度

  • 变长字段的最大字节数 x 每个字节所占字符数 > 255时,如果实际存储长度 <= 127,则用一个字节存储其长度,否则用两个字节存储

为什么这里用了一个127?是为了防止系统不知道这1个字节代表的是半个字符长度,还是1整个字符长度

还是1个字节8个位,0~127时,最高位是0,127~255时,最高位为1,这样就知道这个字节是一个完整的字符长度,还是一个二字节字符长度的一半了

这就是位运算的妙用

结合案例插入的数据,第一条数据是aaaa、bbb、cc、d,变长字段是c1、c2、c4,即存储aaaa、bbb、d的长度,且都是VARCHAR(10),因此都只要1个字节即可,再加上逆序,存储的结果就是:

01 03 04(换算成二进制实际上是00000001 00000011 00000100)

这是在使用ascii字符集的情况下,如果使用的是可变长度字符集utf8,情况又不一样了,在可变长度字符集下,CHAR实际也是可变的,因为utf8的字符存储是1~3个字节,所以分配给CHAR(255)就必须是255~765个字节,也变成可变的了

这种情况下,就要存储aaaa、bbb、cc、d的长度,再加上逆序,存储的结果就是:

01 02 03 04

此外,还有一个条件,不存非NULL值的长度,则第二条数据在ascii字符集和utf8字符集下存储均为:

03 04

注意:变长字段长度列表非必须,没有变长字段,就没有这个值

NULL值记录表

NULL值记录表存储的是可为NULL的列是否为NULL的记录,这里存储是1个位代表一列,且必须是以整字节的格式存储,在一个位中,0代表不为NULL,1代表为NULL,且为逆序存储

上面这段话中,有几个关键点:

  • 什么叫存储可为NULL的列?即非主键、没有被NOT NULL修饰的列,都会被存储进去

  • 1位代表1列,且逆序存储

  • 整字节存储,即不够1个字节,要补全位数

因此以上面案例为例:

c2是NOT NULL列,因此只会存储c1、c3、c4

第一条数据全非null,因此逆序存储0 0 0,一共三位,补齐到一个字节即:

00000000

第二条数据c3、c4为null,因此逆序存储1 1 0,一共三位,补齐到一个字节即:

00000110

如果列数超过8位,例如10位,则补齐到2个字节16位

注意:NULL值记录表是非必须的,如果没有可为NULL的列,那就没有这个字段

记录头信息

是一个定长的5个字节数据,共40位,每个部分有其对应内容:

名称

大小

描述

预留位1

1

没有使用

预留位2

1

没有使用

delete_mask

1

标记该记录是否被删除

min_rec_mask

1

B+树的每层非叶子节点中的最小记录都会添加该标记

n_owned

4

表示当前记录拥有的记录数

heap_no

3

表示当前记录在记录堆的位置信息

record_type

3

表示当前记录的类型, 0 表示普通记录, 1 表示B+树非叶子节点记录, 2 表示最小记录, 3 表示最大记录

next_record

16

表示下一条记录的相对位置

真实记录

DB_ROW_ID

DB_TRX_ID

DB_ROLL_PTR

第1列

第2列

……

真实记录就是话单的真实列数据,但是除了列数据,前面还补了3个隐藏列:

DB_ROW_ID:6字节,非必须,行ID/唯一标识

DB_TRX_ID:必须,6字节,事务ID

DB_ROLL_PTR:必须,7字节,回滚指针

其中DB_ROW_ID比较重要,针对没有设置主键的表,InnoDB会选择一个Unique键做为主键,如果Unique键也没设置,则InnoDB会默认添加DB_ROW_ID列作为隐藏主键

在这三个隐藏列后面,是各列的实际值

Redundant行格式

MySQL5.0以前的格式

格式为:字段长度偏移列表+记录头信息+真实数据

字段长度偏移列表记录的是全字段,偏移指的是记的就不是长度了,而是字段的实际位置,两个字段相减才能算长度

头信息、真实数据都和Compact格式差不多

Compact行格式的行记录上限

对于一条InnoDB真实记录,所有字段相加最大支持65535个字节数据

还记得Compact行格式,真实记录部分,除了三个隐藏列,后面是各列的实际值,其内部还可以再分:

数据长度

NULL值标识

实际数据......

其中:

  • 数据长度占2字节,是一定有的

  • NULL值标识占1字节,如果该列非NULL,是可以没有的

这两个数据和前面的头信息、隐藏列等数据不同,那些是不占行数据总共65535的,而这两个数据是占的

因此假如有一个表只有一个允许为NULL的字段,使用VARCHAR(65535)时,只能支持65535-3=65532个字节,如果是utf字符集的数据,则实际上只能存入65532/3=21844个字符,gbk的话则是65532/2=32766个字符

Compact行格式的页记录上限和行溢出

页是InnoDB的基本单位,每个页16k,即16384个字节,而一个VARCHAR列就可以支持到65532个字节,一页有可能放不下一条记录

更别说InnoDB要求一页中必须存储至少2行

这里说的是至少,因此还有可能更多,假设存储n行,那就是n x 65532个字节

而一页本身就有136字节的固定数据,不随行数变化,还有每行中的27个字节额外信息(即变长字段长度列表、NULL值记录表、记录头信息、3个隐藏列,这些是不占实际列容量的,但是会占页的容量)

现在模拟一个只有1个字段的表,即只有1列,想要存到一页中,假设该页只存储了这一列中的m个字节,因此得到一个公式:

136 + n x (27 + m) > 16384

即一页中除了136个字节固定内容,存了n行,每行有27个字节额外信息和m字节实际数据

当n=2(因为一页至少存2行),可以得到m > 8098时,就得分页了

但是一个VARCHAR最多存65532来着,剩下的部分如何存储呢。Compact格式的解决方案是,只存放768字节的真实数据,后面存放溢出页的地址,而其他数据剩余数据则存在溢出页中

同时,TEXT和BLOBS也会溢出

Dynamic和Compressed行格式

与Compact格式的结构完全相同,只是在行溢出的策略不同

Compact是存768字节真实数据,Dynamic则是一点真实数据都不存,直接存溢出页地址。Dynamic是默认的行格式

Compressed格式则是采用压缩算法对数据进行压缩后再进行存储

0

评论区