InnoDB引擎体系架构

InnoDB引擎架构介绍

innodb存储引擎的体系架构,可简单划分成三层:

  1. 数据文件 :磁盘上的数据文件
  2. 内存池:缓存磁盘上的数据,方便读取,同时在对磁盘文件数据修改之前在这里缓存,然后按一定规刷新到磁盘
  3. 后台线程:主要负责刷新内存池中的数据,保证内存池中都是最近数据。同时将内存中修改的数据刷新到磁盘

磁盘中数据文件如何存放,存放规则等,我们稍后讲解。本章主要说明内存池的模型和后台线程功能,下图展示了InnoDB的体系架构

一、后台进程

InnoDB存储引擎是多线程的模型,因此其后台有多个不同的线程,负责处理不同的任务

1、Master Thread

一个核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲、UNDO页的回收等

2、IO Thread

InnoDB存储引擎大量使用了AIO(Async IO)来处理写IO请求,这样极大地提高了数据库的性能。IO Thread的工作主要是负责这些IO请求的回调处理。共有四种IO Thread,分别是write, read,insert buff,log IO thread。默认的read thread和write thread是四个,可以通过innodb_read_io_threads和innodb_write_io_threads参数来调整线程的数量。通过show engine innodb statusG查看IO thread。

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)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
 ibuf aio reads:, log i/o's:, sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
394 OS file reads, 53 OS file writes, 7 OS fsyncs
11.00 reads/s, 16384 avg bytes/read, 5.00 writes/s, 0.62 fsyncs/s

3、Purge Thread事务完成后,其所使用的undolog可能不在需要,因此Purage Thread来回收已经使用并分配的undo页。InnoDB支持多个purge thread,目的为了进一步加快undo页的回收。同时purge thread离散的读取undo页,这样能更进一步利用磁盘的随机读取性能。

undologUndo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用UndoLog中的备份将数据恢复到事务开始之前的状态。除了可以保证事务的原子性,Undo Log也可以用来辅助完成事务的持久化。

通过innodb_purge_threads参数可以修改Purge Thread的数量

mysql> show variables like '%purge%thread%';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| innodb_purge_threads | 4     |
+----------------------+-------+

4、Page Cleaner Thread

Page Cleaner Thread作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。目的是为了减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。

mysql> show variables like 'innodb_page_cleaners';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| innodb_page_cleaners | 1     |
+----------------------+-------+

二、内存

1、缓冲池

InnoDB存储引擎的基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可将其视为Disk-base Database,由于CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。
缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度对数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,这个过程称为“FIX”在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。(和OS中缓存有些类似?)
对于页修改操作,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘并不是在每次页发生更改时触发,而是通过一种叫Checkpoint的机制刷新回磁盘。这样最也是为了提高数据库的整体性能。
缓冲池的大小会直接影响到数据库的性能,通过配置缓冲池参数innodb_buffer_pool_size可以来设置缓冲池大小。
mysql> show variables like 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
不能简单的认为缓冲池只是缓冲索引页和数据页,它们只是占缓冲池很大一部分而已,其他比如undo页、插入缓冲、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等都会在缓冲池中缓存,以下是InnoDB存储引擎中内存的结构情况:
就允许有多个缓冲池实例。每个页根据哈希值平均到不同缓冲池实例中。这样就减少了数据库内部的资源竞争,增加数据库的并发处理能力。可以通过innodb_buffer_pool_instances参数。
mysql> show variables like 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 8     |
+------------------------------+-------+

 informixmation_schema库的INNODB_BUFFER_POOL_STATS表存放了缓冲池的状态。

 2、LRU List、Free List、Flush List
通常来说,数据库中的缓冲池是通过LRU(Latest Recent Used,最近最少使用)算法来进行管理。访问最频繁的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。缓冲池满时,首先释放尾端页
在InnoDB引擎中,缓冲池页的大小(innodb_page_size)默认是16k。InnoDB引擎对LRU算法做了优化,在LRU中加入了midpoint位置。新读取到的页,并不是放在LRU队列的首部。而是放在LRU队列的midpoint位置。默认配置在LRU列表长度的5/8处,midpoint由参数innodb_old_blocks_pct控制。
mysql> show variables like 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+

37表示新读取的页差在LRU列表尾端的37%位置(差不多3/8)。在InnoDB引擎,midpoint之后的列表称old列表,之前的称new列表。可以理解为new列表中的页都是最为活跃的数据。

如果采用普通的LRU算法,直接将读取到的页放在LRU首部。那么某些SQL操作可能会使缓冲池中的页被刷出,影响缓冲池的效率。例如索引或数据的扫描操作,需要访问许多页甚至全部页,这些页仅在当前查询中需要,并不是热点活跃数据。如果放在LRU首部,可能将需要的热点数据从LRU队列中移除,下一次需要读取该页时,又要从磁盘读取。

为了解决这个问题,引入了innodb_old_blocks_time参数,指定插入到旧子列表中的块在第一次访问后必须在那里停留多长时间(以毫秒为单位),然后才能移动到新子列表。如果值为0,则插入到旧子列表中的块在第一次访问新子列表时立即移动到该子列表,无论插入后多久,访问发生。如果该值大于0,则块将保留在旧子列表中,直到访问发生在在第一次访问之后至少要经过几毫秒。例如,值为1000会导致块停留在旧子列表在第一次访问后1秒钟,然后才有资格移动到新子列表。这样尽可能使LRU列表中的数据不被刷出。

mysql> show variables like 'innodb_old_blocks_time';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000  |
+------------------------+-------+

LRU List来管理已经读取的页。当数据库刚启动,LRU列表是空的。这时页都存放在Free列表中,当需要从缓冲池分页时。首先从Free列表中查看是否有可用的空闲页。若有将该页从Free列表中删除。放入到LRU列表。否则,根据LRU算法,淘汰LRU列表末端的页。将该内存空间分配给新的页。当页从LRU的old部分到new部分时,此时的操作称page made young。如果因为innodb_old_blocks_time导致页没有移动到new的操作称为page not made young。可以通过show engine innodb status来观察LRU列表和Free列表的使用情况和运行状态。

BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 1099431936
Dictionary memory allocated 120760
Buffer pool size   65536
Free buffers       65137
Database pages     399
Old database pages 0
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 364, created 35, written 39
0.07 reads/s, 0.00 creates/s, 0.00 writes/s
Buffer pool hit rate 958 / 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: 399, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

可以看到,Buffer pool size 65536个页。65536*16K = 1G。一个1G的缓冲池。Free buffers表示当前Free列表页的数量,Database pages表示LRU列表的页的数量。可能出现Free buffers与Database pages的数量之和不等于Buffer pool size。因为缓冲池的页还会分配给自适应哈希索引,Lock信息,Insert Buffer等。这部分不需要LRU算法进行维护。

page made young表示LRU中页移动到前端的次数。因为服务器运行阶段没有改变innodb_old_blocks_time的值,所以not young为0,young/s non-youngs/s表示每秒这两个操作的次数。

Buffer pool hit rate 表示缓冲池命中率,通常该值应该不小于95%。如果小于95,可能是因为全表扫描引起LRU队列污染。

注意:show engine innodb status显示的不是数据库的当前状态。而是过去某个时间段的状态,Per second averages calculated from the last 9 seconds代表过去9秒内数据库的状态。还可以通过INNODB_BUFFER_POOL_STATS来检测缓冲池的状态。

通过INNODB_BUFFER_PAGE_LRU来观察每个LRU列表中每个页的具体信息

InnoDB引擎支持压缩页功能,将原本16K的页压缩为1KB 2KB 4KB 8KB。由于页大小发生变化,LRU列表也发生变化。非16K的页,从上边代码可以看出 LRU len: 399, unzip_LRU len: 0

注意LRU的页包含了unzip_LRU中的页。对于压缩页的表,每个表的压缩比率不同,可能为4k或8k。在unzip_LRU列表对不同压缩页大小分别管理。需要申请4KB页的大小。步骤如下

(1)、检查4kb的unzip_LRU列表,检查是否有可用的空闲页
(2)、若有,直接使用
(3)、否则,检查8KB的unzip_LRU列表
(4)、如果得到空闲页,将页分成两个4KB的页,放入到4KB的unzip_LRU列表
(5)、如果不能得到空闲页,从LRU列表申请一个16KB的页,分成1个8KB和两个4KB的页,放到对于的列表中。

在LRU列表中的页被修改后,该页叫做脏页。缓存池中的页和磁盘中的页不一致。数据库通过检查点机制将脏页刷新到磁盘。Flush列表就是脏页列表。需要注意的是,脏页既存在于LRU列表,也存在于Flush列表。LRU列表管理缓冲池中页的可用性,Flush列表管理将页刷回磁盘。Flush列表也可以通过show engine innodb status命令和INNODB_BUFFER_PAGE_LRU表查看。上图中Modified db pages 指的是脏页的数量。

3、重做日志缓冲

InnoDB引擎的内存区除了缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB首先将重做日志信息放到这个缓冲区,然后按照一定频率刷新到重做日志文件。重做日志缓冲不需要太大,一般情况每秒会将重做日志缓存刷新到新的日志文件。用户需要将每秒的的事务量控制在这个缓冲大小之内。该值由innodb_log_buffer_size控制,默认16M。重做日志在以下三种情况刷新到重做日志文件。

(1)、Master Thread每秒将重做日志缓冲刷新到重做日志文件

(2)、每个事务提交时会将重做日志缓冲刷新的到重做日志文件

(3)、重做日志缓冲区空间小于1/2时,重做日志缓冲刷新到重做日志文件。

4、额外的内存池

对一些数据结构本身的内存分配是从额外内存池分配

 三、检查点技术

事务型数据库一般采用Write Ahead Log策略,当事物提交时先写redo log而后修改内存中的页。当数据库宕机对于还未写入磁盘的修改数据可以通过redo log恢复。Checkpoint作用在于保证该点之前的所有修改的页均已刷新到磁盘,这之前的redo log在恢复数据时可以不需要了。

检查点的目的:

(1)、缩短数据库的恢复时间

(2)、缓冲池不够用时,将脏页刷新到磁盘

(3)、重做日志不可用时,刷新脏页。

当数据库宕机后,不需要重做所以的日志。检查点之前的页已经刷新到磁盘,数据库之需要对检查点之后的重做日志进行恢复。大大缩短了恢复时间

当缓冲池不够用时,LRU会溢出最近少使用的页。如果此页为脏页,需要强制执行检查点。

重做日志不可用指的是因为当前事务性数据库对重做日志的设计都是循环使用的。并不是无限增大,重做日志可以被重用指的是这些重做日志已经不再需要,可以被覆盖。若此时重做日志还需要使用,必须强制执行检查点。将缓冲池中的页刷新到当前重做日志的位置。

再InnoDB内部,检查点有两种

(1)、Sharp Checkpoint 发生在数据库关闭时将所有的脏页刷回到磁盘,是默认的工作方式。及参数innodb_fast_shutdown = 1

(2)、Fuzzy Checkpoint 在内部使用这种方式刷新,刷新一部分脏页,不是刷新所有脏页。

在InnoDB引擎可能发生以下几种情况的Fuzzy Checkpoint

(1)、Master Thread Checkpoint  在Master Thread发生的检查点操作,差不多每秒或没十秒从缓冲池的脏页列表刷新一定比例的页回磁盘,此过程是异步的。

(2)、FLUSH_LRU_LIST Chechkpoint 存储引擎需要保证LRU队列有空闲页可用。Page Cleanner Thread会检查LRU列表是否有足够的空间来进行查询操作。倘若没有,将LRU队列尾端的页移除,如果有脏也,执行检查点。用户通过innodb_lru_scan_death参数控制LRU列中可用页的数量,默认值为1024

mysql> show variables like 'innodb_lru_scan_depth';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_lru_scan_depth | 1024  |
+-----------------------+-------+

(3)、Async/Sync Flush Checkpoint 指重做日志不可用的情况。

(4)、Dirty page too much 脏页太多导致强制进行Checkpoint,目的还是保证缓冲池有足够的可用的页。由参数innodb_max_dirty_pages_pct控制。

mysql> show variables like 'innodb_max_dirty_pages_pct';
+----------------------------+-----------+
| Variable_name              | Value     |
+----------------------------+-----------+
| innodb_max_dirty_pages_pct | 75.000000 |
+----------------------------+-----------+

当缓冲池的脏页数量达到75%时,强制执行检查点,刷新一部分脏页到磁盘。

InnoDB内部协调原理:参考 https://www.cnblogs.com/janehoo/p/7717041.html

一条SQL进入MySQL服务器,会依次经过连接池模块(进行鉴权,生成线程),查询缓存模块(是否被缓存过),SQL接口模块(简单的语法校验),查询解析模块,优化器模块(生成语法树),然后再进入innodb存储引擎。进入innodb后,首先会判断该SQL涉及到的页是否存在于缓存中,如果不存在则从磁盘读取相应索引及数据页加载至缓存。如果是select语句,读取数据(使用一致性非锁定读),并将查询结果返回至服务器层。如果是DML语句,读取到相关页,先试图给这个SQL涉及到的记录加锁。加锁成功后,先写undo 页,逻辑地记录这些记录修改前的状态。然后再修改相关记录,这些操作会同步物理地记录至redo log buffer。如果涉及及非唯一辅助索引的更新,还需要使用insert buffer。事务提交时,会启用内部分布式事务,先将SQL语句记录到binlog中,再根据系统设置刷新redo log buffer至redo log,保证binlog与redo log的一致性。提交后,事务会释放对这些记录所加的锁,并将这些修改的记录所在的页放入innodb的flush list中,等待被page cleaner thread刷新到磁盘。这个事务产生的undo page如果没有被其它事务引用(insert的undo page不会被其它事务引用),就会被放入history list中,等待被purge线程回收。

原文地址:https://www.cnblogs.com/jkin/p/10281301.html