MySQL

洞悉MySQL底层架构与SQL调优本质
帅旋
关注
充电
IT宅站长,技术博主,架构师,全网id:arthinking。

InnoDB执行引擎内幕:底层逻辑存储结构

发布于 2020-05-30 | 更新于 2024-05-16

在介绍索引之前,我们有必要了解一下InnoDB底层的逻辑存储结构,因为索引是基于这个底层逻辑存储结构创建的。截止到目前,我们所展示的都仅仅是物理磁盘中的逻辑视图,接下来我们就来看看底层的视图。

1、ibd文件组织结构

现在我们打开一个表空间ibd文件,看看里面都是如何组织数据的?

如下图,表空间由段(segment)、区(extent)、页(page)组成。

InnoDB最小的存储单位是页,默认每个页大小是16k。

而InnoDB存储引擎是面向行的(row-oriented),数据按行进行存放,每个页规定最多允许存放的行数=16k/2 - 200,即7992行。

image-20200523185138539

段:如数据段、索引段、回滚段等。InnoDB存储引擎是B+树索引组织的,所以数据即索引,索引即数据。B+树的叶子节点存储的都是数据段的数据。

2、数据页结构[1]

名称 占用空间 描述
Fil Header 38 byte 页的基本信息,如所属表空间,上一页和下一页指针。
Page Header 56 byte 数据页专有的相关信息
Infimun + Supremum 26 byte 两个虚拟的行记录,用于限定记录的边界
User Records 动态分配 实际存储的行记录内容
Free Space 动态调整 尚未使用的页空间
Page Directory 动态调整 页中某些记录的相对位置
Fil Trailer 8 byte 校验页是否完整

关于Infimun和Supremum:首次创建索引时,InnoDB会在根页面中自动设置一个最小记录和一个最高记录,并且永远不会删除它们。最低记录和最高记录可以视为索引页开销的一部分。最初,它们都存在于根页面上,但是随着索引的增长,最低记录将存在于第一或最低叶子页上,最高记录将出现在最后或最大关键字页上。

image-20200530120303599

3、行记录结构描述[2]

先来讲讲Compact行记录格式,Compact是MySQL5.0引入的,设计目标是高效的存储数据,让一个页能够存放更多的数据,从而实现更快的B+树查找。

名称 描述
变长字段长度列表 字段大小最多用2个字节表示,也就是最多限制长度:2^16=65535个字节;字段大小小于255字节,则用1个字节表示;
NULL标志位 记录该行哪些位置的字段是null值
记录头信息 记录头信息信息,固定占用5个字节
列1数据 实际的列数据,NULL不占用该部分的空间
列2数据

记录头用于将连续的记录链接在一起,并用于行级锁定。

每行数据除了用户定义的列外,还有两个隐藏列:

  • 6个字节的事务ID列;
  • 7个字节的回滚指针列;
  • 如果InnoDB没有指定主键,还会增加一个6个字节的rowid列;

而记录头信息包[3]含如下内容:

名称 大小(bit) 描述
() 1 未知
() 1 未知
deleted_flag 1 该行是否已被删除
min_rec_flag 1 如果该记录是预定义的最小记录,则为1
n_owned 4 该记录拥有的记录数
heap_no 13 索引堆中该条记录的排序号
record_type 3 记录类型:000 普通,001 B+树节点指针,010 Infimum,011 Supremum,1xx 保留
next_record 16 指向页中下一条记录

image-20200530152925354

更详细的页结构参考官网:22.2 InnoDB Page Structure

更详细的行结构参考官网:22.1 InnoDB Record Structure

更详细的行格式参考官网:14.11 InnoDB Row Formats

根据以上格式,可以得出数据页内的记录组织方式:

image-20200530122458403

3.1、MySQL中varchar最大长度是多少

上面表格描述我们知道,一个字段最长限制是65535个字节,这是存储长度的限制。

而MySQL中对存储是有限制的,具体参考:8.4.7 Limits on Table Column Count and Row Size

  • MySQL对每个表有4096列的硬限制,但是对于给定的表,有效最大值可能会更少;
  • MySQL表的每行行最大限制为65,535字节,这是逻辑的限制;实际存储的时候,表的物理最大行大小略小于页面的一半。如果一行的长度少于一页的一半,则所有行都将存储在本地页面内。如果它超过一页的一半,那么将选择可变长度列用于外部页外存储,直到该行大小控制在半页之内为止。

而实际能够存储的字符是跟编码有关的。

背景知识:

  • MySQL 4.0版本以下,varchar(10),代表10个字节,如果存放UTF8汉字,那么只能存3个(每个汉字3字节);

  • MySQL 5.0版本以上,varchar(10),指的是10个字符,无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放10个,最大大小是65532字节

因此,Mysql5根据编码不同,存储大小也不同。

那么假设我们使用的是utf8编码,那么每个字符最多占用3个字节,也就是最多定义varchar(21845)个字符,如果是ascii编码,一个字符相当于一个字节,最多定义varchar(65535)个字符,下面我们验证下。

我们尝试创建一个这样的字段:

1
2
3
4
5
6
7
8
9
CREATE TABLE `t10` ( `id` int(11) NOT NULL,
`a` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB CHARSET=ascii ROW_FORMAT=Compact;


alter table t10 add `str` varchar(21845) DEFAULT NULL;

alter table t10 add `str` varchar(65535) DEFAULT NULL;

发现提示这个错误:

1
2
mysql> alter table t10 add `str` varchar(65535) DEFAULT NULL;
ERROR 1118 (42000): 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

原因是按照以上的行格式介绍,变长字段长度列表记录也需要占用空间,占用2个字节,另外这里是允许为空字段,在8位之内,所以NULL标志位占用1个字节,所以我们总共可以存储的字符数是:

65535 - 2 - 1 - 4 - 4=65534

其中 -2 个字节表示变长字段列表,-1表示NULL标志位,两个-4表示两个int类型字段占用大小

所以实际上能够容纳的varchar大小为:65524,我们验证下:

image-20200524121242172

3.2、行记录超过页大小如何存储

MySQL表的内部表示具有65,535字节的最大行大小限制。InnoDB 对于4KB,8KB,16KB和32KB innodb_page_size 设置,表的最大行大小(适用于本地存储在数据库页面内的数据)略小于页面的一半 。如果包含 可变长度列InnoDB 行超过最大行大小,那么将选择可变长度列用于外部页外存储。

可变长度列由于太长而无法容纳在B树页面上,这个时候会把可变长度列存储在单独分配的磁盘页面上,这些页面称为溢出页面,这些列称为页外列。页外列的值存储在由溢出页面构成的单链接列表中。

InnoDB存储引擎支持四种行格式:REDUNDANTCOMPACTDYNAMIC,和COMPRESSED。不同的行格式,对溢出的阈值和处理方式有所区别,详细参考:14.11 InnoDB Row Formats

COMPACT行格式处理方式

使用COMPACT行格式的表将前768个字节的变长列值(VARCHARVARBINARYBLOBTEXT类型)存储在B树节点内的索引记录中,其余的存储在溢出页上。

如果列的值等于或小于768个字节,则不使用溢出页,因此可以节省一些I / O。

如果查过了768个字节,那么会按照如下方式进行存储:

image-20200524154903041

DYNAMIC行格式处理方式

DYNAMIC行格式提供与COMPACT行格式相同的存储特性,但改进了超长可变长度列的存储能力和支持大索引键前缀。

InnoDB 可以完全在页外存储过长的可变长度列值(针对 VARCHARVARBINARYBLOBTEXT类型),而聚集索引记录仅包含指向溢出页的20字节指针。大于或等于768字节的固定长度字段被编码为可变长度字段。

image-20200524155015374

表中大字段引发的问题

如果一个表中有过多的可变长度大字段,导致一行记录太长,而整个时候使用的是COMPACT行格式,那么就可能会插入数据报错。

如,页面大小是16k,根据前面描述我们知道,MySQL限制一页最少要存储两行数据,如果很多可变长度大字段,在使用COMPACT的情况下,仍然会把大字段的前面768个字节存在索引页中,可以算出最多支持的大字段:1024 * 16 / 2 / 768 = 10.67,那么超过10个可变长度大字段就会插入失败了。

这个时候可以把row format改为:DYNAMIC。

References


  1. MySQL Internals Manual - innodb page structure[EB/OL]. (2020-05-04). Retrieved 2020-0530, from https://dev.mysql.com/doc/internals/en/innodb-page-structure.html ↩︎

  2. official.MySQL Internals Manual - innodb record structure[EB/OL]. (2020-05-04). Retrieved 2020-0530, from https://dev.mysql.com/doc/internals/en/innodb-record-structure.html ↩︎

  3. 姜承尧. MySQL技术内幕-InnoDB存储引擎第二版[M]. 机械工业出版社, 2013-5:104. ↩︎

本文作者: 帅旋

本文链接: https://www.itzhai.com/columns/mysql/innodb/storage-structure.html

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。

请帅旋喝一杯咖啡

咖啡=电量,给帅旋充杯咖啡,他会满电写代码!

IT宅

关注公众号及时获取网站内容更新。