1.InnoDB引擎底层解析
InnoDB的三大特性:
双写机制
Buffer Pool
自适应Hash索引
自适应Hash索引在之前的索引课中已经讲到了,这节课不再做陈述。同时我们对InnoDB不能只是光看亮点,还是要体系化的去学习。
InnoDB的内存结构和磁盘存储结构图总结如下:
看这种结构图大家肯定是比较晕的,所以我们用需求来驱动进行讲解。
1、InnoDB对于我们来说还是一个黑盒,我们只负责使用客户端发送请求并等待服务器返回结果,表中的数据到底存到了哪里?
2、表中的数据以什么格式存放的?
3、InnoDB是以什么方式来访问的这些数据?
4、InnoDB中的事务、锁等的原理是怎样?
1.1.InnoDB记录存储结构和索引页结构
InnoDB是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢,和内存读写差了几个数量级,所以当我们想从表中获取某些记录时,InnoDB存储引擎需要一条一条的把记录从磁盘上读出来么?
InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为行格式。
1.1.1.行格式
InnoDB存储引擎设计了4种不同类型的行格式,分别是Compact、Redundant、Dynamic和Compressed行格式。我们可以查看默认值:
```
show variables like 'innodb_default_row_format'
```
我们可以在创建或修改表的语句中指定行格式:
```
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
```
1.1.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 | 表示当前记录的类型,<br />0表示普通记录,<br />1表示B+树非叶子节点记录,<br />2表示最小记录,<br />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行格式差别不大。
1.1.1.2.Redundant行格式
Redundant行格式是MySQL5.0之前用的一种行格式,不予深究。
1.1.1.3.Dynamic和Compressed行格式
MySQL5.7的默认行格式就是Dynamic,Dynamic和Compressed行格式和Compact行格式挺像,只不过在处理行溢出数据时有所不同。Compressed行格式和Dynamic不同的一点是,Compressed行格式会采用压缩算法对页面进行压缩,以节省空间。
1.1.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个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。
1.1.2.索引页格式
前边我们简单提了一下页的概念,它是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。
InnoDB为了不同的目的而设计了许多种不同类型的页,存放我们表中记录的那种类型的页自然也是其中的一员,官方称这种存放记录的页为索引(INDEX)页,不过要理解成数据页也没问题,毕竟存在着聚簇索引这种索引和数据混合的东西。
1.1.2.1.数据页结构
一个InnoDB数据页的存储空间大致被划分成了7个部分:
| name | 名称 | 长度 | 备注 |
| ------------------ | ------------------ | ---------- | ------------------------ |
| File Header | 文件头部 | 38字节 | 页的一些通用信息 |
| Page Header | 页面头部 | 56字节 | 数据页专有的一些信息 |
| Infimum + Supremum | 最小记录和最大记录 | 26字节 | 两个虚拟的行记录 |
| User Records | 用户记录 | 大小不确定 | 实际存储的行记录内容 |
| Free Space | 空闲空间 | 大小不确