2.1 InnoDB 概述
2.2 InnoDB 存储引擎的版本
2.3 InnoDB 体系架构
MySQL_InnoDB存储引擎体系架构.pngInnoDB 存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作:
- 维护所有进程/线程所需要访问的多个内部数据结构
- 缓存磁盘上的数据,方便快速地读取,同时在对磁盘文件的数据修改之前在这里缓存
- 重做日志 (redo log) 缓冲
==后台线程==的主要作用是负责刷新内存池中的数据,保证缓存池中的内存缓存的是最近的数据。此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下 InnoDB 能恢复到正常运行状态。
2.3.1 后台线程
InnoDB 存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。
- Master Thread
Master Thread 是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲 (INSERT BUFFER)、UNDO 页的回收等。
- IO Thread
在 InnoDB 存储引擎中大量使用了 AIO (Async IO) 来处理写 IO 请求,这样可以极大提高数据库的性能。而 IO Thread 的工作主要是负责这些 IO 请求的回调 (call back) 处理。
可以通过以下参数来查看 read 和 write 线程数量。
mysql> SHOW VARIABLES LIKE 'innodb_version';
+----------------+--------+
| Variable_name | Value |
+----------------+--------+
| innodb_version | 8.0.11 |
+----------------+--------+
1 row in set (0.01 sec)
mysql> SHOW VARIABLES LIKE 'innodb_%io_threads';
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_read_io_threads | 4 |
| innodb_write_io_threads | 4 |
+-------------------------+-------+
2 rows in set (0.00 sec)
可以通过命令 SHOW ENGINE INNODB STATUS;
来观察 InnoDB 中的 IO Thread:
mysql> SHOW ENGINE INNODB STATUS;
...
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
- Purge Thread
事务被提交后,其所使用的 undolog 可能不再需要,因此需要 PurgeThread 来回收已经使用并分配的 undo 页。
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 8.0.11 |
+-----------+
1 row in set (0.00 sec)
mysql> SHOW VARIABLES LIKE 'innodb_purge_threads';
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| innodb_purge_threads | 4 |
+----------------------+-------+
1 row in set (0.01 sec)
- Page Cleaner Thread
Page Cleaner Thread 是在 InnoDB 1.2.x 版本中引入的。其作用是将之前版本中的脏页的刷新操作都放入到单独的线程中来完成。
2.3.2 内存
- 缓冲池
InnoDB 存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。在数据库系统中,由于 CPU 速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。
在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页 "FIX" 在缓冲池中。下一次再读取相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
对于数据库中页的修改操作,则首先修改缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为 Checkpoint 的机制刷新回磁盘。
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name | Value |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set (0.01 sec)
MySQL_InnoDB内存数据对象.png
缓冲池中主要缓存的数据对象为数据页和索引页。
从 InnoDB 1.0.x 版本开始,允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 1 |
+------------------------------+-------+
1 row in set (0.00 sec)
在配置文件中将 innodb_buffer_pool_instances
设置为大于 1 的值就可以得到多个缓冲池实例。
使用以下命令来查看缓冲池状态:
mysql> use information_schema
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> SELECT POOL_ID,POOL_SIZE,FREE_BUFFERS,DATABASE_PAGES FROM INNODB_BUFFER_POOL_STATS;
+---------+-----------+--------------+----------------+
| POOL_ID | POOL_SIZE | FREE_BUFFERS | DATABASE_PAGES |
+---------+-----------+--------------+----------------+
| 0 | 8191 | 6969 | 1216 |
+---------+-----------+--------------+----------------+
1 row in set (0.00 sec)
- LRU List、Free List 和 Flush List
通常来说,数据库中的缓冲池是通过 LRU (Latest Recent Used,最近最少使用) 算法来进行管理的。
稍有不同的是 InnoDB 存储引擎对传统的 LRU 算法做了一些优化。在 InnoDB 的存储引擎中,LRU 列表中还加入了 midpoint 位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到 LRU 列表的首部,而是放入到 LRU 列表的 midpoint 位置。
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37 |
+-----------------------+-------+
1 row in set (0.00 sec)
从上面的例子可以看到,参数 innodb_old_blocks_pct 默认值为 37%,表示新读取的页插入到 LRU 列表尾端的 37% 的位置。
- 重做日志缓冲
InnnoDB 存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲 (redo log buffer)。InnoDB 存储引擎首先将重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件。
重做日志缓冲一般不需要设置得很大,因为一般情况下每一秒钟会将重做日志刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。
mysql> SHOW VARIABLES LIKE 'innodb_log_buffer_size';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
1 row in set (0.00 sec)
重做日志在下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中:
- Master Thread 每一秒将重做日志缓冲刷新到重做日志文件;
- 每个事务提交时会将重做日志缓冲刷新到重做日志文件;
- 当重做日志缓冲池剩余空间小于 1/2 时,重做日志缓冲刷新到重做日志文件。
- 额外的内存池
在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。例如,分配了缓冲池 (innodb_buffer_pool),但是每个缓冲池中的帧缓冲 (frame buffer) 还有对应的缓冲控制对象 (buffer control block),这些对象记录了一些诸如 LRU、锁、等待等信息,而==这个对象的内存需要从额外内存池中申请==。
因此,当申请了很大的 InnoDB 缓冲池时,也应考虑相应增加这个值。
2.4 Checkpoint 技术
缓冲池的设计目的是为了协调 CPU 速度和磁盘速度的鸿沟,因此页的操作首先都是在缓冲池中完成的。如果一条 DML 语句,如 Update 或 Delete 改变了页中的记录,那么此时页都是脏的,即缓冲池中的页的版本要比磁盘的新。数据库需要将新版本的页从缓冲池刷新到磁盘。
为了避免数据丢失的问题,当前事务数据库系统普遍采用了 ==Write Ahead Log 策略==,即当事务提交时,先写重做日志,再修改页。当由于发生宕机而导致数据丢失时,通过重做日志来完成数据的恢复。
Checkpoint (检查点) 技术的目的是解决以下几个问题:
- 缩短数据库的恢复时间;
- 缓冲池不够用时,将脏页面刷新到磁盘;
- 重做日志不可用时,刷新脏页。
mysql> SHOW ENGINE INNODB STATUS;
...
---
LOG
---
Log sequence number 52000952
Log buffer assigned up to 52000952
Log buffer completed up to 52000952
Log written up to 52000952
Log flushed up to 52000952
Added dirty pages up to 52000952
Pages flushed up to 52000952
Last checkpoint at 52000952
11 log i/o's done, 0.00 log i/o's/second
----------------------
在 InnoDB 存储引擎内部,有两种 Checkpoint:
- Sharp Checkpoint
- Fuzzy Checkpoint
Sharp Checkpoint 发生在数据库关闭时将所有的脏页面都刷新回磁盘,这是默认的工作方式,即参数 innodb_fast_shutdown=1
。
在 InnoDB 存储引擎内部使用 Fuzzy Checkpoint 进行页的刷新,即只刷新一部分脏页,而不是刷新所有的脏页回磁盘。在 InnoDB 存储引擎中可能发生如下几种情况的 Fuzzy Checkpoint:
- Master Thread Checkpoint
- FLUSH_LRU_LIST Checkpoint
- Async/Sync Flush Checkpoint
- Dirty Page too much Checkpoint
2.5 Master Thread 工作方式
InnoDB 存储引擎的主要工作都是在一个单独的后台线程 Master Thread 中完成的。
2.5.1 InnoDB 1.0x 版本之前的 Master Thread
Master Thread 具有最高的线程优先级别。其内部由多个循环 (loop) 组成:主循环 (loop)、后台循环 (backgroup loop)、刷新循环 (flush loop)、暂停循环 (suspend loop)。Master Thread 会根据数据库运行的状态在 loop、backgroup loop、flush loop、suspend loop 中进行切换。
Master Thread 完整的伪代码:
2.5.2 InnoDB 1.2.x 版本之前的 Master Thread
查看当前 Master Thread 的状态信息:
mysql> SHOW ENGINE INNODB STATUS;
...
| InnoDB | |
=====================================
2019-05-03 12:22:08 0x70000b6ee000 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 57 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 3 srv_active, 0 srv_shutdown, 11791 srv_idle
srv_master_thread log flush and writes: 0
2.5.3 InnoDB 1.2.x 版本的 Master Thread
对于刷新脏页的操作,从 Master Thread 线程分离到一个单独的 Page Cleaner Thread,从而减轻了 Master Thread 的工作,同时进一步提高了系统的并发性。
2.6 InnoDB 关键特性
InnoDB 存储引擎的关机键特性包括:
- 插入缓存 (Insert Buffer)
- 两次写 (Double Write)
- 自适应哈希索引 (Adaptive Hash Index)
- 异步 IO (Async IO)
- 刷新邻接页 (Flush Neighbour Page)
==2.6.1 插入缓存==
-
Insert Buffer
-
Change Buffer
-
Insert Buffer 的内部实现
-
Merge Insert Buffer
2.6.2 两次写
在应用 (apply) 重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是 doublewrite。
MySQL_doublewrite.pngdoublewrite 由两部分组成,一部分是内存中的 doublewrite buffer,大小为 2MB,另一部分是物理磁盘上共享表空间中连续的 128 个页,即 2 个区 (extent),大小同样为 2MB。在对缓冲池的脏页面进行刷新时,并不直接写磁盘,而是会通过 memcpy 函数将脏页面先复制到内存中的 doublewrite buffer,之后通过 doublewrite buffer 再分两次,每次 1 MB 顺序地写入==共享表空间的物理磁盘上==,然后马上调用 fsync 函数,同步磁盘,避免缓冲写带来的问题。
如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB 存储引擎可以从共享表空间中的 doublewrite 中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。
参数 skip_innodb_doublewrite
可以禁止使用 doublewrite 功能,这时可能会发生写失效问题。
2.6.3 自适应哈希索引
InnoDB 存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引 (Adaptive Hash Index,AHI)。
InnoDB 存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。 AHI 有一个要求,即对这个页的连续==访问模式==必须是一样的。值得注意的是,哈希索引只能用来搜索==等值的查询==。
可以通过观察 SHOW ENGINE INNODB STATUS;
的结果及参数 innodb_adaptive_hash_index
来考虑是禁用或启动此特性,默认 AHI 为开启状态。
2.6.4 异步 IO
为了提高磁盘操作性能,当前的数据库系统都采用异步 IO (Asynchronous IO,AIO) 的方式来处理磁盘操作。
2.6.5 刷新邻接页
其工作原理为:当刷新一个脏页时,InnoDB 存储引擎会检测该页所在区 (extent) 的所有页,如果是脏页,那么一起进行刷新。这样做的好处是,通过 AIO 可以将多个 IO 写入操作合并为一个 IO 操作。
InnoDB 存储引擎从 1.2.x 版本开始提供了 innodb_flush_neighbors
,用来控制是否启用该特性。
对于传统机械硬盘建议启用该特性,而对于固态硬盘有着超高的 IOPS 性能的磁盘,则建议将该参数设置为 0,即关闭此特性。
2.7 启动、关闭与恢复
在关闭时,参数 innodb_fast_shutdown
影响着表的存储引擎为 InnoDB 的行为。该参数可取为 0、1、2,默认为 1。
- 0 表示在 MySQL 数据库关闭时,InnoDB 需要完成所有的 full purge 和 merge insert buffer,并且将所有的脏页刷新回磁盘。
- 1 表示不需要完成上述的 full purge 和 merge insert buffer 操作,但是在缓冲池中的一些数据脏页还是会刷新回磁盘。
- 2 表示不完成 full purge 和 merge insert buffer 操作,也不将缓冲池中的数据脏页写回磁盘,而是将日志都写入日志文件。这样不会有任何事务的丢失,但是下次 MySQL 数据库启动时,会进行恢复操作 (recovery)。
参数 innodb_force_recovery
影响了整个 InnoDB 存储引擎恢复的状况。可以设置为 6 个非零值:1~6。大的数字表示包含了前面所有小数字表示的影响。
- 1(SRV_FORCE_IGNORE_CORRUPT):忽略检查到的 corrupt。
- 2(SRV_FORCE_NO_BACKGROUND):阻止 Master Thread 线程的运行,如 Master Thread 线程需要进行 full purge 操作,而这会导致 crash。
- 3(SRV_FORCE_NO_TRX_UNDO):不进行事务的回滚操作。
- 4(SRV_FORCE_NO_IBUF_MERGE):不进行插入缓冲的合并操作。
- 5(SRV_FORCE_NO_UNDO_LOG_SCAN):不查看撤销日志 (Undo Log),InnoDB 存储引擎会将未提交的事务视为已提交。
- 6(SRV_FORCE_NO_LOG_REDO):不进行前滚操作。
网友评论