InnoDB 引擎底层存储和缓存原理
到目前为止,MySQL 对于我们来说还是一个黑盒,我们只负责使用客户端发 送请求并等待服务器返回结果,表中的数据到底存到了哪里?以什么格式存放的? MySQL 是以什么方式来访问的这些数据?这些问题我们统统不知道。要搞明白 查询优化背后的原理,就必须深入 MySQL 的底层去一探究竟,而且事务、锁等 的原理也要求我们必须深入底层。
InnoDB 记录存储结构和索引页结构
InnoDB 是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启 我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把 磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中 的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢,和内存读写差了几个 数量级,所以当我们想从表中获取某些记录时,InnoDB 存储引擎需要一条一条 的把记录从磁盘上读出来么? InnoDB 采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交 互的基本单位,InnoDB 中页的大小一般为 16 KB。也就是在一般情况下,一次最 少从磁盘中读取 16KB 的内容到内存中,一次最少把内存中的 16KB 内容刷新到 磁盘中。 我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方 式也被称为行格式或者记录格式。InnoDB 存储引擎设计了 4 种不同类型的行格 式,分别是 Compact、Redundant、Dynamic 和 Compressed 行格式。
行格式
我们可以在创建或修改表的语句中指定行格式: CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
COMPACT
我们知道MySQL支持一些变长的数据类型,比如VARCHAR(M)、VARBINARY(M)、 各种 TEXT 类型,各种 BLOB 类型,我们也可以把拥有这些数据类型的列称为变 长字段,变长字段中存储多少字节的数据是不固定的,所以我们在存储真实数据 的时候需要顺便把这些数据占用的字节数也存起来。如果该可变字段允许存储的 最大字节数超过 255 字节并且真实存储的字节数超过 127 字节,则使用 2 个字节, 否则使用 1 个字节。 表中的某些列可能存储 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,唯一标识一条记录 DB_TRX_ID:必须,6 字节,表示事务 ID DB_ROLL_PTR:必须,7 字节,表示回滚指针 InnoDB 表对主键的生成策略是:优先使用用户自定义主键作为主键,如果用 户没有定义主键,则选取一个 Unique 键作为主键,如果表中连 Unique 键都没有 定义的话,则 InnoDB 会为表默认添加一个名为 row_id 的隐藏列作为主键。 DB_TRX_ID(也可以称为 trx_id) 和 DB_ROLL_PTR(也可以称为 roll_ptr) 这两 个列是必有的,但是 row_id 是可选的(在没有自定义主键以及 Unique 键的情 况下才会添加该列)。 其他的行格式和 Compact 行格式差别不大。
MySQL 基础架构
下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到客户端的一条 SQL 语句在 MySQL 内部是如何执行的。
从上图可以看出, MySQL 主要由下面几部分构成:
- 连接器: 身份认证和权限相关(登录 MySQL 的时候)。
- 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
- 分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
- 优化器: 按照 MySQL 认为最优的方案去执行。
- 执行器: 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。
- 插件式存储引擎:主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。
MySQL 存储引擎
MySQL 核心在于存储引擎,想要深入学习 MySQL,必定要深入研究 MySQL 存储引擎。
MySQL 支持多种存储引擎,你可以通过 SHOW ENGINES
命令来查看 MySQL 支持的所有存储引擎。
从上图我们可以查看出, MySQL 当前默认的存储引擎是 InnoDB。并且,所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
我这里使用的 MySQL 版本是 8.x,不同的 MySQL 版本之间可能会有差别。
MySQL 5.5.5 之前,MyISAM 是 MySQL 的默认存储引擎。5.5.5 版本之后,InnoDB 是 MySQL 的默认存储引擎。
你可以通过 SELECT VERSION()
命令查看你的 MySQL 版本。
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 8.0.27 |
+-----------+
1 row in set (0.00 sec)
你也可以通过 SHOW VARIABLES LIKE '%storage_engine%'
命令直接查看 MySQL 当前默认的存储引擎。
mysql> SHOW VARIABLES LIKE '%storage_engine%';
+---------------------------------+-----------+
| Variable_name | Value |
+---------------------------------+-----------+
| default_storage_engine | InnoDB |
| default_tmp_storage_engine | InnoDB |
| disabled_storage_engines | |
| internal_tmp_mem_storage_engine | TempTable |
+---------------------------------+-----------+
4 rows in set (0.00 sec)
如果你想要深入了解每个存储引擎以及它们之间的区别,推荐你去阅读以下 MySQL 官方文档对应的介绍(面试不会问这么细,了解即可):
- InnoDB 存储引擎详细介绍:https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html 。
- 其他存储引擎详细介绍:https://dev.mysql.com/doc/refman/8.0/en/storage-engines.html 。