从 MySQL 5.5
版本开始默认使用 InnoDB
作为引擎,它擅长处理事务,具有自动崩溃恢复的特性,在日常开发中使用非常广泛。下面是官方的 InnoDB
引擎架构图,主要分为内存结构和磁盘结构两大部分。
1. InnoDB 内存结构
内存结构主要包括 Buffer Pool
、Change Buffer
、Adaptive Hash Index
和 Log Buffer
四大组件。
SHOW ENGINE INNODB STATUS;
Database pages + Free buffers <= Buffer pool size
(其它空间有可能分配给自适应索引和 changeBuffer
)
1.1 Buffer Pool
缓冲池,简称 BP
。BP
以 Page
页为单位,默认大小 16K
,BP
的底层采用链表数据结构管理 Page
。在 InnoDB
访问表记录和索引时会在 Page
页中缓存,以后使用可以减少磁盘 IO
操作,提升效率。
1.1.1 Page 管理机制
Page
根据状态可以分为三种类型:
-
free page
: 空闲page
,未被使用 -
clean page
:被使用page
,数据没有被修改过 -
dirty page
:脏页,被使用page
,数据被修改过,页中数据和磁盘的数据产生了不一致
针对上述三种 page
类型,InnoDB
通过三种链表结构来维护和管理
-
free list
:表示空闲缓冲区,管理free page
-
flush list
:表示需要刷新到磁盘的缓冲区,管理dirty page
,内部page
按修改时间排序。脏页既存在于flush链表
,也在LRU链表
中,但是两种互不影响,LRU链表
负责管理page
的可用性和释放,而flush链表
负责管理脏页的刷盘操作。 -
lru list
:表示正在使用的缓冲区,管理clean page
和dirty page
,缓冲区以midpoint
为基点,前面链表称为new
列表区,存放经常访问的数据,占63%
;后 面的链表称为old
列表区,存放使用较少数据,占37%
。
1.1.2 改进型 LRU 算法维护
- 普通
LRU
:末尾淘汰法,新数据从链表头部加入,释放空间时从末尾淘汰 - 改进
LRU
:链表分为new
和old
两个部分,加入元素时并不是从表头插入,而是从中间midpoint
位置插入,如果数据很快被访问,那么page
就会向new
列表头部移动,如果数据没有被访问,会逐步向old
尾部移动,等待淘汰。
每当有新的 page
数据读取到 buffer pool
时,InnoDB
引擎会判断是否有空闲页,是否足够,如果有就将 free page
从 free list
列表删除,放入到 LRU
列表中。没有空闲页,就会根据 LRU
算法淘汰 LRU
链表默认的页,将内存空间释放分配给新的页。
1.1.3 Buffer Pool 配置参数
-- 查看page页大小
show variables like '%innodb_page_size%';
-- 查看 lru list 中 old 列表参数
show variables like '%innodb_old%';
-- 查看 buffer pool 参数
show variables like '%innodb_buffer%';
建议:将 innodb_buffer_pool_size
设置为总内存大小的 60%-80%
, innodb_buffer_pool_instances
可以设置为多个,这样可以避免缓存争夺。
1.2 Change Buffer
写缓冲区,简称 CB
。在进行 DML
操作时,如果 BP
没有其相应的 Page
数据, 并不会立刻将磁盘页加载到缓冲池,而是在 CB
记录缓冲变更,等未来数据被读取时,再将数据合并恢复到 BP
中。
ChangeBuffer
占用 BufferPool
空间,默认占25%
,大允许占50%
,可以根据读写业务量来 进行调整。参数 innodb_change_buffer_max_size
;
show variables like '%innodb_change_buffer_max_size%';
当更新一条记录时,该记录在 BufferPool
存在,直接在 BufferPool
修改,一次内存操作。如果该记录在 BufferPool
不存在(没有命中),会直接在 ChangeBuffer
进行一次内存操作,不用再去磁盘查询数据,避免一次磁盘IO
。当下次查询记录时,会先进性磁盘读取,然后再从 ChangeBuffer
中读取信息合并,最终载入BufferPool
中。
写缓冲区,仅适用于非唯一普通索引页,为什么?
如果在索引设置唯一性,在进行修改时,InnoDB
必须要做唯一性校验,因此必须查询磁盘, 做一次 IO
操作。会直接将记录查询到 BufferPool
中,然后在缓冲池修改,不会在ChangeBuffer
操作。
1.3 Adaptive Hash Index
自适应哈希索引,用于优化对 BP
数据的查询。InnoDB
存储引擎会监 控对表索引的查找,如果观察到建立哈希索引可以带来速度的提升,则建立哈希索引,所以称之为自适应。InnoDB
存储引擎会自动根据访问的频率和模式来为某些页建立哈希索引。
1.4 Log Buffer
日志缓冲区,用来保存要写入磁盘上 log
文件(Redo/Undo
)的数据,日志缓冲区的内容定期刷新到磁盘 log
文件中。日志缓冲区满时会自动将其刷新到磁盘,当遇到 BLOB
或多行更新的大事务操作时,增加日志缓冲区可以节省磁盘 I/O
。
LogBuffer
主要是用于记录 InnoDB
引擎日志,在 DML
操作时会产生 Redo
和Undo
日志。LogBuffer
空间满了,会自动写入磁盘。可以通过将 innodb_log_buffer_size
参数调大,减少磁盘IO
频率
show variables like '%innodb_log_buffer_size%';
show variables like '%innodb_log%';
SHOW VARIABLES LIKE '%innodb_flush_log_at_trx_commit%'
innodb_flush_log_at_trx_commit
参数控制日志刷新行为,默认为1
-
0 :
每隔1
秒写日志文件和刷盘操作(写日志文件LogBuffer -> OS cache
,刷盘OS cache -> 磁盘文件
),最多丢失1
秒数据 -
1:
事务提交,立刻写日志文件和刷盘,数据不丢失,但是会频繁IO
操作 -
2:
事务提交,立刻写日志文件,每隔1
秒钟进行刷盘操作
2. 磁盘结构
InnoDB
磁盘主要包含Tablespaces
,InnoDB Data Dictionary
,Doublewrite Buffer
、Redo Log
和 Undo Logs
。
2.1 表空间(Tablespaces)
用于存储表结构和数据。表空间又分为系统表空间、独立表空间、 通用表空间、临时表空间、Undo
表空间等多种类型;
2.1.1 系统表空间(The System Tablespace)
包含 InnoDB
数据字典,Doublewrite Buffer
,Change Buffer
,Undo Logs
的存储区域。系统表空间也默认包含任何用户在系统表空间创建的表数据和索引数据。系统表空间是一个共享的表空间因为它是被多个表共享的。该空间的数据文件通过参数 innodb_data_file_path
控制,默认值是 ibdata1:12M:autoextend
(文件名为ibdata1
、 12MB
、自动扩展)。
SHOW VARIABLES LIKE '%innodb_data_file_path%'
2.1.2 独立表空间(File-Per-Table Tablespaces)
默认开启,独立表空间是一个单表表空间,该表创建于自己的数据文件中,而非创建于系统表空间中。当 innodb_file_per_table
选项开启时,表将被创建于表空间中。否则, innodb
将被创建于系统表空间中。每个表文件表空间由一个 .ibd
数据文件代表,该文件默认被创建于数据库目录中。表空间的表文件支持动态(dynamic
)和压缩 (commpressed
)行格式。
SHOW VARIABLES LIKE '%innodb_file_per_table%'
2.1.3 通用表空间(General Tablespaces)
通用表空间为通过 create tablespace
语法创建的共享表空间。通用表空间可以创建于 mysql
数据目录外的其他表空间,其可以容纳多张表,且其支持所有的行格式。
-- 创建表空 间ts1
CREATE TABLESPACE ts1 ADD DATAFILE ts1.ibd Engine=InnoDB;
-- 将表添加到 ts1 表空间
CREATE TABLE t1 (c1 INT PRIMARY KEY) TABLESPACE ts1;
2.1.4 撤销表空间(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
等。
SHOW VARIABLES LIKE '%innodb_undo_tablespaces%'
2.1.5 临时表空间(Temporary Tablespaces)
分为 session temporary tablespaces
和 global temporary tablespace
两种。session temporary tablespaces
存储的是用户创建的临时表和磁盘内部的临时表。global temporary tablespace
储存用户临时表的回滚段(rollback segments
)。mysql
服务器正常关闭或异常终止时,临时表空间将被移除,每次启动时会被重新创建。
2.2 数据字典(InnoDB Data Dictionary)
InnoDB
数据字典由内部系统表组成,这些表包含用于查找表、索引和表字段等对象的元数 据。元数据物理上位于InnoDB
系统表空间中。由于历史原因,数据字典元数据在一定程度上 与InnoDB
表元数据文件(.frm
文件)中存储的信息重叠。
2.3 双写缓冲区(Doublewrite Buffer)
位于系统表空间,是一个存储区域。在 BufferPage
的 page
页刷新到磁盘真正的位置前,会先将数据存在Doublewrite
缓冲区。如果在 page
页写入过程中出现操作系统、存储子系统或 mysqld
进程崩溃,InnoDB
可以在崩溃恢复期间从Doublewrite
缓冲区中找到页的一个备份。在大多数情况下,默认情况下启用双写缓冲区;要禁用 Doublewrite
缓冲区,可以将 innodb_doublewrite
设置为0
。使用Doublewrite
缓冲区时建议将 innodb_flush_method
设置为 O_DIRECT
。
SHOW VARIABLES LIKE '%innodb_doublewrite%'
SHOW VARIABLES LIKE '%innodb_flush_method%'
MySQL 的 innodb_flush_method 这个参数控制着 innodb 数据文件及 redo log 的打开、 刷写模式。有三个值:fdatasync(默认),O_DSYNC,O_DIRECT。
设置 O_DIRECT 表示数据文件写入操作会通知操作系统不要缓存数据,也不要用预读,直接从Innodb Buffer 写到磁盘文件。
默认的 fdatasync 意思是先写入操作系统缓存,然后再调用 fsync() 函数去异步刷数据文件与redo log 的缓存信息。
2.4 重做日志(Redo Log)
重做日志是一种基于磁盘的数据结构,用于在崩溃恢复期间更正不完整事务写入的数据。 MySQL
以循环方式写入重做日志文件,记录 InnoDB
中所有对 Buffer Pool
修改的日志。当出现实例故障(像断电),导致数据未能更新到数据文件,则数据库重启时须 redo
,重新把数据更新到数据文件。读写事务在执行的过程中,都会不断的产生 redo log
。默认情况下,重做日志在磁盘上由两个名为 ib_logfile0
和 ib_logfile1
的文件物理表示。
2.5 撤销日志(Undo Logs)
撤消日志是在事务开始之前保存的被修改数据的备份,用于例外情况时回滚事务。撤消日志属于逻辑日志,根据每行记录进行记录。撤消日志存在于系统表空间、撤消表空间和临时表空间中。
3. 新版本结构演变
3.1 MySQL 5.7 版本
- 将
Undo
日志表空间从共享表空间ibdata
文件中分离出来,可以在安装MySQL
时由用户自行指定文件大小和数量。 - 增加了
temporary
临时表空间,里面存储着临时表或临时查询结果集的数据。 -
Buffer Pool
大小可以动态修改,无需重启数据库实例。
3.2 MySQL 8.0 版本
- 将
InnoDB
表的数据字典和Undo
都从共享表空间ibdata
中彻底分离出来了,以前需要ibdata
中数据字典与独立表空间ibd
文件中数据字典一致才行,8.0
版本就不需要了。 -
temporary
临时表空间也可以配置多个物理文件,而且均为InnoDB
存储引擎并能创建索引,这样加快了处理的速度。 - 用户可以像
Oracle
数据库那样设置一些表空间,每个表空间对应多个物理文件,每个表空间可以给多个表使用,但一个表只能存储在一个表空间中。 - 将
Doublewrite Buffer
从共享表空间ibdata
中也分离出来了。