文章目录
第二章 InnoDB存储引擎
InnoDB是事务安全的MySQL存储引擎,设计上采用了类似于Oracle数据库的架构。通常来说,InnoDB存储引擎是OLTP应用中核心表的首选存储引擎。
1 InnoDB存储引擎概述
InnoDB存储引擎是第一个完整支持ACID事务的MySQL存储引擎,其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效的利用以及使用内存和CPU。
2 InnoDB存储引擎的版本
InnoDB存储引擎被包含所有MySQL数据库的二进制发现版本中。早期InnoDB随着MySQL数据库更新而更新,从MySQL 5.1开始,MySQL数据库允许存储引擎以动态方式加载引擎,这样存储引擎的更新可以不受MySQL数据库版本的限制。所以MySQL 5.1版本有两个InnoDB版本,一个是静态编译的InnoDB版本,一个是动态加载的InnoDB版本(可以将其视为InnoDB 1.0.x版本),后续InnoDB版本一直在升级并添加新的功能。
# 查看当前使用MySQL的版本
mysql> show variables like 'innodb_version'\G;
*************************** 1. row ***************************
Variable_name: innodb_version
Value: 5.7.38
1 row in set (0.01 sec)
3 InnoDB体系架构
如图所示,InnoDB存储引擎有多个内存块,可以人为这些内存块组成一个大的内存池,负责如下工作:
- 维护所有进程/线程需要访问的多个内部数据结构。
- 缓存磁盘上的数据,方便快速的读取,同时在对磁盘文件的数据修改之前在这里缓存。
- 重做日志(redo log)缓冲。
- …
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能恢复到正常运行的状态。
3.1 后台线程
InnoDB存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。Master Thread
、IO Thread
、Purge Thread
、Page Cleaner Thread
等。
1.Master Thread
核心的后台线程,负责将缓冲池中的数据异步刷新到磁盘,保证数据一致性,包括脏页的属性、合并插入缓冲、UNDO页的回收
2.IO Thread
在InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,极大提高数据库性能。而IO Thread住哟处理这些IO请求的回调处理。
InnoDB 1.0之前有4个IO Thread,分别是write
,read
,insert buffer
,log IO thread
。Linux平台下,IO Thread的数量不能进行调整,但是在Windows平台下可以通过innodb_file_io_threas
参数来增加IO Thread。
InnoDB 1.0.x版本开始,read thread和write thread分别增大到4个,并且不再使用innodb_file_io_threads
参数,而是分别使用innodb_read_io_threads
和innodb_write_io_threads
参数进行设置。
# 查看read thread和write thread的数量
mysql> show variables like 'innodb_%io_threads'\G;
*************************** 1. row ***************************
Variable_name: innodb_read_io_threads
Value: 4
*************************** 2. row ***************************
Variable_name: innodb_write_io_threads
Value: 4
2 rows in set (0.00 sec)
使用show engine innodb status\G
可以查看InnoDB中的IO Thread:
mysql> show engine innodb status\G;
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
......
--------
FILE I/O
--------
I/O thread 0 state: waiting for completed aio requests (insert buffer thread)
I/O thread 1 state: waiting for completed aio requests (log thread)
I/O thread 2 state: waiting for completed aio requests (read thread)
I/O thread 3 state: waiting for completed aio requests (read thread)
I/O thread 4 state: waiting for completed aio requests (read thread)
I/O thread 5 state: waiting for completed aio requests (read thread)
I/O thread 6 state: waiting for completed aio requests (write thread)
I/O thread 7 state: waiting for completed aio requests (write thread)
I/O thread 8 state: waiting for completed aio requests (write thread)
I/O thread 9 state: waiting for completed aio requests (write thread)
.......
从查询结果可以看出,thread 0是insert buffer thread,thread 1是log thread,thread2-5是read thread,thread 6-9是write thread。
3.Purge Thread
事务被提交后,其所使用的undolog可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页。在InnoDB 1.1版本之前,purge操作仅在InnoDB存储引擎的Master Thread中完成。而从InnoDB 1.1版本开始,purge操作可以独立到单独的线程中进行,以减轻Master Thread的工作,从而提高CPU的使用率以及提升存储引擎的性能。用户可以在MySQL数据库配置文件中添加以下配置来启用独立的Purge Thread:
# my.cnf文件中
[mysqld]
innodb_purge_threads=1
InnoDB 1.2版本之后,为了加快undo页的回收,进一步利用磁盘的随机读写性能,InnoDB开始支持多个Purge Thread:
mysql> show variables like 'innodb_purge_threads'\G;
*************************** 1. row ***************************
Variable_name: innodb_purge_threads
Value: 4
1 row in set (0.05 sec)
3.2 内存
1.缓存池
InnoDB存储引擎是基于磁盘存储的,并将其中记录按照页的方式进行管理。因此可以将其视为基于磁盘的数据库
。
在数据库系统中,由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库整体性能
。
缓冲池
简单点说就是一块内存区域
,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。
数据库中读取页的操作流程是:首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页"FIX"在缓冲池中。下一次再读相同页的时候,首先判断该页是否存在缓冲池中。如在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
数据库中修改操作流程是:首先修改在缓冲池中的页,然后以一定频率刷新到磁盘上。需要注意的是,页从缓冲池刷新回到磁盘的操作并不是每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回到磁盘。同样,也是为了提高数据库的整体性能。
对于InnocentDB存储引擎而言,缓冲池的配置通过参数innodb_buffer_pool_size来设置。
mysql> show variables like 'innodb_buffer_pool_size'\G;
*************************** 1. row ***************************
Variable_name: innodb_buffer_pool_size
Value: 134217728
1 row in set (0.00 sec)
具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓存(insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)、数据字典信息(data dictionary)等。不能简单的认为,缓冲池只是缓存索引页和数据页,他们只是占缓冲池很大一部分而已。
从InnoDB 1.0.x版本开始,允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力。可通过innodb_buffer_pool_instances
进行配置,默认为1。
mysql> show variables like 'innodb_buffer_pool_instances'\G;
*************************** 1. row ***************************
Variable_name: innodb_buffer_pool_instances
Value: 1
1 row in set (0.00 sec)
在配置文件中将innodb_buffer_pool_instances
设置为大于1的值就可以得到多个缓冲池实例。需要注意的是,当配置多个缓冲池实例时,仅当innodb_buffer_pool_size
大小设置为1GB或更大的时候,此选项才生效。指定的总大小将分配给所有缓冲池。为了获得最佳效率,指定组合innodb_buffer_pool_instances
和innodb_buffer_pool_size
,使得每个缓冲池实例至少是1GB。再通过命令show engine innodb status
即可观察。
mysql> show engine innodb status\G;
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2022-11-16 14:51:11 0x7f7f6005a700 INNODB MONITOR OUTPUT
=====================================
......
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 117329
Buffer pool size 8191
Free buffers 7622
Database pages 566
Old database pages 228
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 532, created 34, written 36
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
Buffer pool hit rate 407 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 566, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
......
从MySQL 5.6版本开始,还可以通过information_schema架构下的表Innodb_buffer_pool_stats来观察缓存状态
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\G;
*************************** 1. row ***************************
pool_id: 0
pool_size: 8191
free_buffers: 7652
database_pages: 536
1 row in set (0.00 sec)
2.LRU List、Free List、Flush List
从上一节我们了解到缓冲池是一个很大的内存区域,其中存放各自类型的页。那么InnoDB存储引擎是怎么对这么大的内存区域进行管理的呢?
通常来说,数据库中的缓冲池是通过LRU(Laster Recent Used,最近最少使用)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。
在InnoDB存储引擎中,缓冲池中页的大小默认为16KB,同样使用LRU算法对缓冲池进行管理。稍有不同的是InnoDB存储引擎对传统LRU算法做了一些优化。在InnoDB的存储引擎中,LRU列表还加入了midpoint位置。新读取到的页,虽然是最新访问的页,但并不能直接放入LRU列表的首部,而是放入到LRU列表的midpoint位置。这个位置在InnoDB存储引擎下称为midpoint insertion strategy
。在默认配置下,该位置在LRU列表长度的5/8处。midpoint位置可由参数innodb_old_blocks_pct控制,如:
mysql> show variables like 'innodb_old_blocks_pct'\G;
*************************** 1. row ***************************
Variable_name: innodb_old_blocks_pct
Value: 37
1 row in set (0.01 sec)
如上查询所示:参数innodb_old_blocks_pct
默认值为37,表示新读取的页插入到LRU列表尾端37%的位置。在InnoDB存储引擎中,把midpoint之后的列表称为old列表,之前的列表称