MySQL 的 InnoDB 存储结构

·  阅读 253

这节我们着重去学习 InnoDB 存储引擎。主要从 InnoDB 的存储结构、线程模型、数据文件这几个方面做下深入的理解。

InnoDB 的存储结构

InnoDB 的内存结构

在 MySQL 5.5 版本之后开始默认使用了 InnoDB 存储引擎。InnoDB 存储引擎擅长处理事务且具有自动崩溃回复的特性。下面我们看下 InnoDB 存储引擎的架构图(主要分为内存结构、磁盘结构)

InnoDB 的内存结构主要包括:

  • Buffer Pool
  • Change Buffer
  • Adaptive Hash Index
  • Log Buffer

Buffer Pool

缓冲池,简称 BP。以 Page 为单位默认大小为 16K底层数据结构采用的链表,用链表来管理 Page。InnoDB 访问表记录和索引的时候会在 Page 页中添加缓存(为了减少磁盘的 IO 操作)。可见 Page 的重要性,我们看下如何对 Page 做设计的。

Page 的管理机制

Page 用三种状态表示:

  • free page:未被使用的空闲的页
  • clean page:被使用的页,但是页中的数据没有被修改过
  • dirty page:脏页,被使用过的页,页中的数据被修改过且和磁盘中的数据不一致

针对上述三种状态的页,InnoDB 通过三种链表结构来维护和管理的。

  • free list:表示空闲的缓冲区,用来管理 free page
  • flush list:表示需要刷新数据到磁盘中的缓冲区,主要用于管理 dirty page
    • 内部的 page 按照修改时间进行排序的
    • dirty 页 存在于 flush list,也存在于 lru list,但是两种互不影响
      • flush list 主要负责管理 dirty page 的刷怕操作
      • lru list 主要负责管理 page 的可用性和释放
  • lru list:表示正在使用的缓冲区,主要用于管理 clean page 和 dirty page。
    • 缓冲区以 midpoint 为基点,前面的链表称为 new 列表区(用于存放经常访问的数据),占 63%
    • 后面的链表称为 old 列表区,用于存放访问较少的数据,占 37%
LRU 算法改进
  • 普通的 LRU 算法:采用的是末尾淘汰法,将新数据从链表头部加入,释放空间时从链表的末尾淘汰数据
  • 改进后的 LRU 算法
    • 将链表分为 new 和 old 两个部分,加入新元素的时候不是从表头插入的,而是从中间的 midpoint 位置插入新元素。如果数据很快的就会被访问会将 page 向 new 链表的头部移动。如果数据没有被访问,会逐步的被移动到 old 尾部区域,等待被淘汰。
    • 每当有新的 page 数据被读取到 buffer pool 的时候,InnoDB 因会判断是否有空闲页或者空闲页是否足够
      • 如果空闲页足够:会将 free page 从 free list 列表中删除并放入到 LRU 列表中
      • 如果空闲页不够:会根据 LRU 算法淘汰掉 LRU 链表默认的页,将内存空间释放并分配给新的页

Change Buffer

写缓冲区,简称 CB。在我们对数据进行 DML 操作时,如果 BP 没有其相应的 page 数据,并不会立刻将磁盘页加载到 Buffer Pool(缓冲池)中,而是在 CB 中记录缓冲的变更,等到数据被读取时,再将数据合并然后恢复到 BP 中

CB 占用的是 BP 的空间,默认值占用 25%,最大运行占 50%。我们可以根据读写的业务量来进行调整。关键参数是 innodb_change_buffer_max_size

每当我们更新一条记录时,该记录如果在 BP 中,便会直接在 BP 中修改,一次内存操作。如果该记录在 BP 中不存在(没有命中),便会直接在 CB 中进行一次内存操作(不用去磁盘查询数据,避免一次磁盘的 IO)。当下次查询记录的时候,会先进磁盘中读取然后再从 CB 中读取信息并合并,最终加载到 BP 中。

写缓冲区仅适用于非唯一普通索引页,why?

如果在索引中设置了唯一,在进行修改的时候,InnoDB 必然要做唯一性的校验,所以必须会查询磁盘做一次磁盘的 IO。这个时候会直接将记录查询并加载到 BP 中,然后在 BP 中进行修改(不会在 CP 中操作)。

Adaptive Hash Index

自适应哈希索引,主要用于对 BP 处查询数据做优化。

InnoDB 存储引擎会监控对表索引的查找,如果观察到建立哈希索引可以带来速度上的提升时,则会建立哈希索引,称之为自适应。InnoDB 会自动根据访问的频率模式选择为某些页建立哈希索引。

Log Buffer

日志缓冲区,主要用于保存将要写入磁盘上 log 文件(Redo/Undo)的数据。日志缓存区中的内容会定期刷新到磁盘的 log 文件中。当日志缓冲区满的时候会自动将其刷新到新的磁盘中,每当遇到 BLOB 或 多行更新的大事务操作时,增加日志缓冲区大小可以减少磁盘的 IO。

Log Buffer 主要用于记录 InnoDB 引擎日志,在 DML 操作的时候会产生 Redo 和 Undo 日志。我们可以通过参数 innodb_log_buffer_size 将日志缓冲区调大,从而减少磁盘的 IO 操作。

-- 设置日志缓冲区大小的参数
innodb_log_buffer_size
复制代码

当然 InnoDB 还提供了可以控制日志刷新行为的参数 innodb_flush_log_at_trx_commmit,默认值为 1,具体如下:

innodb_flush_log_at_trx_commmit
复制代码
  • innodb_flush_log_at_trx_commmit = 0 时:每隔 1 秒写日志文件和刷盘操作,最多会丢失 1s 的数据。
    • 写日志文件 LogBuffer --> OS cache,刷盘 OS cache --> 磁盘文件
  • innodb_flush_log_at_trx_commmit = 1 时:事务提交,立刻写日志文件和刷盘,数据不会丢失,但是会频繁的进行 IO 操作。
  • innodb_flush_log_at_trx_commmit = 2 时:事务提交,立刻写日志文件,每隔 1s 进行刷盘操作。

InnoDB 的磁盘结构

InnoDB 的磁盘结构主要包含:

  • Tablespaces

  • InnoDB Data Dictionary

  • Doublewrite Buffer

  • Redo Log

  • Undo Logs

Tablespaces

表空间,主要用于存储表结构和数据。

其中表空间又有不同的划分多种类型:

  • 系统表空间
  • 独立表空间
  • 通用表空间
  • 临时表空间
  • Undo 表空间
系统表空间(System Tablespace)

系统表空间包含了 InnoDB 中的数据字典、Doublewrite Buffer、Change Buffer、Undo Logs 的存储区域。默认包含任何用户在系统表空间创建的表数据和索引数据。系统表空间是一个共享的空间,可以被过个表共享。该空间的数据文件可以通过参数 innodb_data_file_path 进行控制。默认值是 ibdata 1:12M:autoextend(文件名为 ibdata1、大小为 12 M、自动扩展)

独立表空间(File-Per-Table Tablespaces)

默认是开启的,独立表空间是一个单表空间,这个表在自己的数据文件中创建,并不在系统表空间中创建。当 innodb_file_per_table 选中开启时,表将被创建到表空间中。否认 InnboDB 将被创建到系统表空间中。

每个表文件表空间由一个 .idb 数据文件代表,该文件默认被创建于数据库目录中。表空间中的表文件支持动态(dynamic)和压缩(commpressed)行格式。

通用表空间(General Tablespaces)

通用表空间是通过 create tablespace 语法创建的共享表空间,可以容纳多张表且支持所有的行格式。通用表空间也可以创建于 MySQL 数据目录外的其它表空间。

-- 创建表空间 ts1
CREATE TABLESPACE ts1 ADD DATAFILE ts1.idb Engine=InnoDB;
-- 将 t1表添加到 ts1 表空间中
CREATE TABLE t1(c1 INT PRIMARY KEY) TABLESPACE ts1;
复制代码
撤销表空间(Undo Tablespaces)

撤销表空间是由一个或多个包含 Undo 日志文件组成的。在MySQL 5.7版本之前 Undo 占用的是 System Tablespace 共享区,从 5.7 开始将 Undo 从 System Tablespace 中分离了出来。InnoDB使用的 Undo 表空间可以通过 innodb_undo_tablespaces 配置选项进行控制,其默认为 0(为 0 时表示使用系统表空间 ibdata1;大于 0 时表示使用 Undo 表空间 undo_001、undo_002 等 )。

数据字典(InnoDB Data Dictionary)

InnoDB 数据字典有内部系统表构成。这些表包含用于查找表、索引和表字段等对象的元数据。元数据物理上位于 InnoDB 系统表空间中。由于历史原因,数据字典元数据在一定程度上与 InnoDB 表元数据文件(.frm 文件)中存储的信息重叠。

双写缓冲区(Doublewrite Buffer)

位于系统表空间,是一个存储区域。

在 BufferPage 的 page 页刷新到磁盘中真正的位置前,会先将数据存在 Doublewrite 缓冲区。如果在 page 页写入过程中出现操作系统、存储子系统或 mysqld 进程崩溃时,InnoDB 可以在崩溃恢复期间从 Doublewrite 缓冲区中找到一个好的页面的备份。在大多数情况下,默认情况下启用双写缓冲区,要禁用 Doublewrite 缓冲区,可以通过参数 innodb_doublewrite 设置为 0 时表示禁用双写缓冲区。使用 Doublewrite 缓冲区时建议将参数innodb_flush_method 设置为 O_DIRECT。 具体原因我们看下官方给出的解释:

MySQL的innodb_flush_method这个参数控制着innodb数据文件及redo log的打开、刷写模式。有三个值:fdatasync(默认),O_DSYNC,O_DIRECT。设置O_DIRECT表示数据文件写入操作会通知操作系统不要缓存数据,也不要用预读,直接从Innodb Buffer写到磁盘文件。默认的fdatasync意思是先写入操作系统缓存,然后再调用fsync()函数去异步刷数据文件与redo log的缓存信息。
复制代码
重做日志(Redo Log)

重做日志是一种基于磁盘的数据结构,用于在崩溃恢复期间更正不完整事务写入的数据。MySQL 会以循环方式写入 Redo Log 文件,记录 InnoDB 中所有对 Buffer Pool 修改的日志。当出现实例故障(像断电),导致数据未能更新到数据文件时,数据库重启时会进行 Redo 操作,重新把数据更新到数据文件中。读写事务在执行的过程中,都会不断的产生 Redo Log。默认情况下,Redo Log 在磁盘上由两个文件进行物理表示(ib_logfile0 文件和 ib_logfile1)。

撤销日志(Undo Logs)

撤消日志属于逻辑日志,是在事务开始之前保存的被修改数据的备份,主要用于例外情况时回滚事务。根据每行记录进行记录。

撤消日志存在于系统表空间、撤消表空间和临时表空间中

MySQL 8.0 版本后结构的改变

  • MySQL 5.7 版本
    • 将 Undo日志表空间从共享表空间 ibdata 文件中分离出来,可以在安装 MySQL 时由用户自行指定文件大小和数量。
    • 增加了 temporary 临时表空间,里面存储着临时表或临时查询结果集的数据。
    • Buffer Pool 大小可以动态修改后无需重启数据库实例。
  • MySQL 8.0 版本
    • 将 InnoDB 表的数据字典和 Undo Log 都从共享表空间 ibdata 中彻底分离出来了,以前需要将 ibdata 中的数据字典与独立表空间 ibd 文件中的数据字典保持一致,8.0 版本就不需要了。
    • temporary 临时表空间也可以配置多个物理文件,而且均为 InnoDB 存储引擎并能创建索引,加快了处理的速度。
    • 用户可以像 Oracle 数据库那样设置一些表空间,每个表空间对应多个物理文件每个表空间可以给多个表使用,但一个表只能存储在一个表空间中。
    • 将 Doublewrite Buffer 从共享表空间 ibdata 中分离了出来。
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改