MySQL-InnoDB引擎底层解析

目录

1. InnoDB的三大特性:

2.InnoDB记录存储结构和索引页结构

2.1.行格式

2.1.1.COMPACT

2.1.2.Redundant行格式

2.1.3.Dynamic和Compressed行格式

2.1.4. 数据溢出

2.2.索引页格式

2.2.1.数据页结构

User Records

Page Directory

Page Header

File Header

File Trailer

3.InnoDB的表空间

3.1.独立表空间结构

3.1.1. 区(extent)

3.1.2. 段(segment)

3.2.系统表空间

3.2.1. 整体结构

3.2.2. 双写缓冲区/双写机制

3.2.3.InnoDB数据字典(Data Dictionary Header)

4.InnoDB的Buffer Pool

4.1.缓存的重要性

4.2.Buffer Pool

4.2.1. free链表的管理

4.2.2. 缓存页的哈希处理

4.2.3. flush链表的管理

4.2.4. LRU链表的管理

缓存不够的窘境

简单的LRU链表

划分区域的LRU链表

线性预读

随机预读

更进一步优化LRU链表

4.3.多个Buffer Pool实例

4.4. innodb_buffer_pool_chunk_size

4.5. 查看Buffer Pool的状态信息

4.5.InnoDB的内存结构总结


1. InnoDB的三大特性:

  • 双写机制

  • Buffer Pool

  • 自适应Hash索引

自适应Hash索引在之前的索引课中已经讲到了,这节课不再做陈述。同时我们对InnoDB不能只是光看亮点,还是要体系化的去学习。

InnoDB的内存结构和磁盘存储结构图总结如下:

看这种结构图大家肯定是比较晕的,所以我们用需求来驱动进行讲解。

1、InnoDB对于我们来说还是一个黑盒,我们只负责使用客户端发送请求并等待服务器返回结果,表中的数据到底存到了哪里?

2、表中的数据以什么格式存放的?

3、InnoDB是以什么方式来访问的这些数据?

4、InnoDB中的事务、锁等的原理是怎样?

2.InnoDB记录存储结构和索引页结构

InnoDB是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢,和内存读写差了几个数量级,所以当我们想从表中获取某些记录时,InnoDB存储引擎需要一条一条的把记录从磁盘上读出来么?

InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。

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

2.1.行格式

InnoDB存储引擎设计了4种不同类型的行格式,分别是Compact、Redundant、Dynamic和Compressed行格式。我们可以查看默认值:

show variables like 'innodb_default_row_format'

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

CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
2.1.1.COMPACT
create table text(c1 VARCHAR(10)) ROW_FORMAT=COMPACT;

COMPACT行格式示意图如下:

变长字段长度列表

我们知道MySQL支持一些变长的数据类型,比如VARCHAR(M)、VARBINARY(M)、各种TEXT类型,各种BLOB类型,我们也可以把拥有这些数据类型的列称为变长字段,变长字段中存储多少字节的数据是不固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存起来。如果该可变字段允许存储的最大字节数(M×W)超过255字节并且真实存储的字节数(L)超过127字节,则使用2个字节,否则使用1个字节。

NULL值列表

表中的某些列可能存储NULL值,如果把这些NULL值都放到记录的真实数据中存储会很占地方,所以Compact行格式把这些值为NULL的列统一管理起来,存储到NULL值列表。每个允许存储NULL的列对应一个二进制位,二进制位的值为1时,代表该列的值为NULL。二进制位的值为0时,代表该列的值不为NULL。

记录头信息

它是由固定的5个字节组成。5个字节也就是40个二进制位,不同的位代表不同的意思。

二进制位数 解释
预留位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 表示下一条记录的相对位置

隐藏列信息

MySQL会为每个记录默认的添加一些列(也称为隐藏列),包括:

DB_ROW_ID(row_id):非必须,6字节,表示行ID,唯一标识一条记录

InnoDB表对主键的生成策略是:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。

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

DB_ROLL_PTR:必须,7字节,表示回滚指

其他的行格式和Compact行格式差别不大。

2.1.2.Redundant行格式

Redundant行格式是MySQL5.0之前用的一种行格式,不予深究。

2.1.3.Dynamic和Compressed行格式

MySQL5.7的默认行格式就是Dynamic,Dynamic和Compressed行格式和Compact行格式挺像,只不过在处理行溢出数据时有所不同。Compressed行格式和Dynamic不同的一点是,Compressed行格式会采用压缩算法对页面进行压缩,以节省空间。

2.1.4. 数据溢出

如果我们定义一个表,表中只有一个VARCHAR字段,如下:

CREATE TABLE test_varchar( c VARCHAR(60000) )

然后往这个字段插入60000个字符,会发生什么?

前边说过,MySQL中磁盘和内存交互的基本单位是页,也就是说MySQL是以页为基本单位来管理存储空间的,我们的记录都会被分配到某个页中存储。而一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65532个字节,这样就可能造成一个页存放不了一条记录的情况。

在Compact和Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的该列的前768个字节的数据,然后把剩余的数据分散存储在几个其他的页中,记录的真实数据处用20个字节存储指向这些页的地址。这个过程也叫做行溢出,存储超出768字节的那些页面也被称为溢出页。

Dynamic和Compressed行格式,不会在记录的真实数据处存储字段真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。

2.2.索引页格式

前边我们简单提了一下页的概念,它是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。

InnoDB为了不同的目的而设计了许多种不同类型的页,存放我们表中记录的那种类型的页自然也是其中的一员,官方称这种存放记录的页为索引(INDEX)页,不过要理解成数据页也没问题,毕竟存在着聚簇索引这种索引和数据混合的东西。

2.2.1.数据页结构

一个InnoDB数据页的存储空间大致被划分成了7个部分:

name 名称 长度 备注
File Header 文件头部 38字节 页的一些通用信息
Page Header 页面头部 56字节 数据页专有的一些信息
Infimum + Supremum 最小记录和最大记录 26字节 两个虚拟的行记录
User Records 用户记录 大小不确定 实际存储的行记录内容
Free Space 空闲空间 大小不确定 页中尚未使用的空间
Page Directory 页面目录 大小不确定 页中的某些记录的相对位置
File Trailer 文件尾部 8字节 校验页是否完整
User Records

我们自己存储的记录会按照我们指定的行格式存储到User Records部分。但是在一开始生成页的时候,其实并没有User Records这个部分,每当我们插入一条记录,都会从Free Space部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。

当前记录被删除时,则会修改记录头信息中的delete_mask为1,也就是说被删除的记录还在页中,还在真实的磁盘上。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗。

所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为所谓的可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

同时我们插入的记录在会记录自己在本页中的位置,写入了记录头信息中heap_no部分。heap_no值为0和1的记录是InnoDB自动给每个页增加的两个记录,称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录,一个代表最大记录,这两条存放在页的User Records部分,他们被单独放在一个称为Infimum + Supremum的部分。

记录头信息中next_record记录了从当前记录的真实数据到下一条记录的真实数据的地址偏移量。这其实是个链表,可以通过一条记录找到它的下一条记录。但是需要注意注意再注意的一点是,下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录)

我们的记录按照主键从小到大的顺序形成了一个单链表,记录被删除,则从这个链表上摘除。

Page Directory

Page Directory主要是解决记录链表的查找问题。如果我们想根据主键值查找页中的某条记录该咋办?按链表查找的办法:从Infimum记录(最小记录)开始,沿着链表一直往后找,总会找到或者找不到。

InnoDB的改进是,为页中的记录再制作了一个目录,他们的制作过程是这样的:

1、将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。

2、每个组的最后一条记录(也就是组内最大的那条

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

yongge

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

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

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

打赏作者

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

抵扣说明:

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

余额充值