Innodb存储引擎原理(一)

MySQL概述

​ 当启动实例时,MySQL数据库会去读取配置文件,根据配置文件的参数来启动数据库实例。如果没有配置文件会按照编译时默认参数设置启动实例。用以下命令可以查看当mysql数据库实例启动时,会在哪些位置查找配置文件。

mysql --help | grep my.cnf

img

​ 可以看到MySQL数据库按/etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf ~/my.cnf的顺序读取配置文件。如果多个配置文件都有同一个参数,MySQL数据库会以读取到最后一个配置文件中的参数为准。

img

​ 从图1-1可以看出MySQL由以下几个部分组成:

  • 连接池组件

  • 管理服务和工具组件

  • SQL接口组件

  • 查询分析器组件

  • 优化器组件

  • 缓冲(Cache)组件

  • 插件式存储引擎

  • 物理文件

​ 从图1-1可以我们还可以发现,MySQL数据库区别于其他的数据库最重要的一个特点就是就是其插件式的表存储引擎。存储引擎是基于表的,而不是基于数据库的

插件式引擎

MyISAM存储引擎

​ MyISAM不支持事务,表锁设计,支持全文索引,MySQL 5.5.8之前MyISAM是默认的存储引擎(Windows除外)。MyISAM存储引擎的缓冲池(cache)只缓存索引文件,而不缓冲数据文件。

​ MyISAM存储引擎表由MYD和MYI组成,MYD用于存放数据文件,MYI用于存放索引文件。可以使用myisampack工具来进一步压缩数据文件,因为myisampack工具使用哈夫曼编码静态算法来压缩数据,因此使用myisampack压缩的表是只读的,用户也可以使用myisampack来解压数据文件。

​ 在MySQL 5.0版本之前,MyISAM默认支持的表大小为4GB,如果需要支持大于4GB的MyISAM表时,则需要定制MAX_ROWS和AVG_ROW_LENGTH属性。从MySQL 5.0开始,MyISAM默认支持256TB的单表数据,这足够满足一般应用需求。

img

NDB存储引擎

​ NDB是一个集群存储引擎,其结构是share nothing的集群架构,因此能提供更高的可用性。NDB的特点是数据全部放在内存中(从MySQL 5.1开始可以将非索引数据存放在磁盘上),因此主键查找的速度极快,并且通过添加NDB数据存储节点可以线性地提高数据库性能性能,是高可用、高性能的集群系统。

关于NDB存储引擎有一个问题值得注意,那就是NDB存储引擎的连接操作(JOIN)是在MySQL数据库层完成的,而不是在存储引擎层完成的。这意味着复杂的连接操作需要巨大的网络开销,因此查询速度很慢。

Memory存储引擎

​ memory存储引擎(之前称HEAP存储引擎)将表中的数据存放在内存中,如果数据库发生重启或者崩溃,则表中的数据将全部消失。它非常适合用于存储临时数据的临时表以及数据仓库中的纬度表。Memory存储引擎默认使用哈希索引,而不是我们熟知的B+树索引。

​ 虽然memory存储引擎速度非常快,但在使用中还是有一定限制的,比如只支持表锁,并发性能差,并且不支持TEXT和BLOB列类型。最重要的是,存储变长字段(varchar)是按照定长字段(char)的方式进行的,因此会浪费内存。

​ MySQL数据库使用Memory存储引擎作为临时表来存放查询的中间结果集。如果中间结果集大于Memory存储引擎表的容量设置,又或者中间结果集含有TEXT和BLOB列类型字段,则MySQL数据库会将其转换到MyISAM存储引擎表存放到磁盘中。之前提到MyISAM不缓存数据文件,因此这时产生的临时表的性能对查询会有损失。

各存储引擎之间的比较

img

可以用过 SHOW ENGINES 语句来查看当前使用的MySQL数据库所支持的存储引擎,也可以通过 information_schema 架构下的 ENGINES 表,如下所示:

img

InnoDB存储引擎

InnoDB体系架构

img

从图可见InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作:

  • 维护所有进程/线程需要访问的多个内部数据结构。

  • 缓存磁盘数据,方便快速地读取,同时在对磁盘数据文件的修改之前在这里缓存。

  • 重做日志(redo log)缓存。

  • ......

​ 后台线程主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存是最近的数据。此外将已修改的内存文件刷新到磁盘文件,同时保证数据库在发生异常的情况下,InnoDB能恢复到正常运行的状态。

后台线程

​ InnoDB是多线程的模型,因此后台有多个不同的线程,负责处理不同的任务

Master Thread

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

IO Thread

​ 在InnoDB存储引擎中大量使用了AIO来处理写IO请求,这样可以极大提高数据库的性能。而IO Thread的工作主要负责这些IO请求的回调(callback)处理。InnoDB 1.0版本以前共有4个IO Thread,分别是read、write、insert buff和log IO Thread。在Linux平台下,IO Thread的数量不能进行调整,但是在Windows平台下可以通过参数innodb_file_io_threads来增大IO Thread。从InnoDB 1.0.x版本开始,read thread和write thread分别增大到4个,并且不能使用innodb_file_io_threads参数,而是分别使用innodb_read_io_threads和innodb_write_io_threads参数进行设置,如:

img

Purge Thread

​ 事务被提交后,其所使用的undolog可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页。在InnoDB 1.1版本之前,purge操作仅在InnoDB存储引擎的Master Thread中完成。而从InnoDB 1.1版本开始,purge操作可以独立到单独的线程中进行,以此减轻Master Thread的工作,从而提高CPU的使用率以及提升存储引擎的性能。用户可以在MySQL数据库的配置文件中添加如下命令来启用独立的Purge Thread:

[mysqld]

innodb_purge_threads=1

在InnoDB 1.1版本中,即使将innodb_purge_threads设为大于1,InnoDB存储一青年启动时也会将其设为1,并在错误文件中出现如下类似的提示:

img

​ 从InnoDB 1.2版本开始,InnoDB支持多个Purge Thread,这样做的目的是为了进一步加快undo页的回收。同时由于Purge Thread需要离散地读取undo页,这样也能更进一步利用磁盘的随机读取性能。

Page Cleaner Thread

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

内存

缓冲池

​ InnoDB存储引擎是基于磁盘储存的,并将其中的记录按照页的方式进行管理。因此可将其视为基于磁盘的数据库系统。在数据库系统中,由于CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。

​ 缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读取到的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则读取磁盘上的页。

​ 对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。同样,这也是为了提高数据库的整体性能。

​ 对于InnoDB存储引擎而言,其缓冲池的配置通过参数innodb_buffer_pool_size来设置。下面显示一台MySQL数据库服务器,其将InnoDB存储引擎的缓冲池设置为15GB。

show variables like 'innodb_buffer_pool_size'\G;

img

​ 具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)、数据字典信息等。不能简单的认为,缓冲池只是缓存索引页和数据页,它们只是占缓冲池很大的一部分而已。图2-2很好地显示了InnoDB存储引擎中内存的结构情况。

img

​ 从InnoDB 1.0.x版本开始,允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力。可以通过参数innodb_buffer_pool_instances来进行配置,改值默认为1。

img

通过命令SHOW ENGINE INNODB STATUS可以观察到每个缓冲池实例对象的运行情况。

LRU List、Free List和Flush List

​ 通常来说,数据库中的缓冲池通过LRU(Last 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 控制,如:

show variables like 'innodb_old_blocks_pct'\G

img

​ 从上面的例子可以看到,参数 innodb_old_blocks_pct 默认值为37.表示新读取的页插入到LRU列表尾端的37%的位置(差不多3/8的位置)。在InnoDB存储引擎中,把midpoint之后的列表称为old列表,之前的列表成为new列表。可以简单地理解为new列表中的页都是最活跃的热点数据。

​ 那为什么不采用朴素的LRU算法,直接将读取的页放入到LRU列表的首部呢?这是因为若直接将读取到的页放入到LRU的首部,那么某些SQL操作可能会使缓冲池中的页被刷新出,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫描操作。这类操作需要访问表中的许多页,甚至是全部的页,而这些页通常来说又仅在这次查询操作中需要,并不是活跃的热点数据。如果页被放入LRU列表的首部,那么非常可能将所需的热点数据页从LRU列表中移除,而在下一次需要读取该页时,InnoDB存储引擎需要再次访问磁盘。

​ 为了解决这个问题,InnoDB存储引擎引入了另一个参数来进一步管理LRU列表,这个参数是innodb_old_blocks_time,用于表示读取到mid位置后需要等待多久才会被加入到LRU列表的热端。刚才当需要执行上述所说的SQL操作时,可以通过下面的方法尽可能使LRU列表中的热点数据不被刷出。

set global innodb_old_blocks_time=1000;

img

如果用户预估自己活跃的热点数据不止63%,那么在执行SQL语句前,可以通过下面的语句来减少热点页可能被刷出的概率。

set global innodb_old_blocks_pct=20;

img

​ LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何的页。这时页都放在Free列表中。当需要从缓冲池分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。当页从LRU列表的old部分加入到new部分时,称此时发生的操作为page made young,而因为innodb_old_blocks_time的设置而导致页没有从old部分移动到new部分的操作称为page not made young。可以通过命令SHOW ENGINE INNODB STATUS来观察LRU列表以及Free列表的使用情况和运行状态。

show engine innodb status\G;

img

​ 通过命令SHOW ENGINE INNODB STATUS可以看到:当前Buffer pool size共有327679个页,即327679*16K,总共5GB的缓冲池。Free buffers表示当前Free列表中页的数量。Database pages表示LRU列表中页的数量。可能的情况是Free buffers与Database pages的数量之和不等于Buffer pool size。正如图2-2所示的那样,因为缓冲池中的页还可能被分配给自适应哈希索引、Lock信息、Insert Buffer等页,而这部分页不需要LRU算法进行维护,因此不存在LRU列表中。

​ pages made young显示了LRU列表中页移动到前端的次数,因为该服务器在运行阶段没有改变innodb_old_blocks_time的值,因此not young为0。young/s、non-young/s表示每秒这两类操作的次数。这里还有一个重要的观察变量——Buffer pool hit rate,表示缓冲池的命中率,这个例子中为100%,说明缓冲池运行状态非常良好。通常该值不应该小于95%。若发生Buffer pool hit rate的值小于95%这种情况,用户需要观察是否是由于全部扫描引起的LRU列表被污染的问题。

​ 从Innodb 1.2版本开始,还可以通过表INNODB_BUFFER_POOL_STATS来观察缓冲池的运行状态,如:

img

​ 此外,还可以通过表INNODB_BUFFER_PAGE_LRU来观察每个LRU列表中每个页的具体信息,例如通过下面的语句可以看到缓冲池中LRU列表中SPACE为1的表的页类型:

img img

​ InnoDB存储引擎从1.0.x版本开始支持压缩页的功能,即将原本16KB的页压缩为1KB、2KB、4KB和8KB。而由于页的大小发生了变换,LRU列表也有了些许的改变。对于非16KB的页,是通过unzip_LRU列表进行管理的。通过命令SHOW ENGINE INNODB STATUS可以观察到如下内容:

img

​ 可以看到LRU列表中一共有1539个页,而unzip_LRU列表中有156个页。这里需要注意的是,LRU中的页包含了unzip_LRU列表中的页面。对于压缩页的表,每个表的压缩比率可能各不相同。可能存在有的表页大小为8KB,有的表页大小为2KB的情况。unzip_LRU是怎么从缓冲池中分配内存的呢?

​ 首先,在unzip_LRU列表中对不同压缩页大小的页进行分别管理。其次,通过伙伴算法进行内存的分配。例如对需要从缓冲池中申请页为4KB的大小,其过程如下:

1)检查4KB的unzip_LRU列表,检查是否有可用的空闲页面;

2)若有,则直接使用;

3)否则,检查8KB的unzip_LRU列表;

4)若能够得到空闲页,将页分为2个4KB页,存放到4KB的unzip_LRU列表;

5)若不能的到空闲页,从LRU列表中申请一个16KB的页,将页分为1个8KB的页、2个4Kb的页,分别存放在对应的unzip_LRU列表中。

​ 同样可以通过information_schema架构下的表INNODB_BUFFER_PAGE_LRU来观察unzip_LRU列表中的页,如:

img

​ 在LRU列表中的页被修改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。这时数据库会通过CHECKPOINT机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。需要注意的是,脏页既存在于LRU列表中,也存在于Flush列表中。LRU列表用来管理缓冲池中页的可用性,FLush列表用来管理将页刷新到磁盘,二者互不影响。

​ 同LRU列表一样,Flush列表也可以通过命令SHOW ENGINE INNODB STATUS来查看,前面例子中Modified db pages 24673就显示了脏页的数量。information_schema架构下并没有类似INNODB_BUFFER_PAGE_LRU的表来显示脏页的数量及脏页的类型,但正如前面所述的那样,脏页同样存在于LRU列表中,故用户可以用过元数据表INNODB_BUFFER_PAGE_LRU来查看,唯一不同的是需要加入OLDEST_MODIFICATION大于0的SQL查询条件,如:

img

可以看到当前共有5个脏页及它们对应的表和页的类型。TABLE_NAME为NULL表示该页属于系统表空间。

重做日志缓冲

​ 从图2-2可以看到,InnoDB存储引擎的内存区域出了有缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件。重做日志缓冲一般不需要设置得很大,因为一般情况下每一秒会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。该值可由配置参数innodb_log_buffer_size控制,默认为8MB:

img

​ 在通常情况下,8MB的重做日志缓冲池足以满足绝大部分的应用,因为重做日志在下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中。

  • Master Thread每一秒将重做日志缓冲刷新到重做日志文件;

  • 每个事务提交时会将重做日志缓冲刷新到重做日志文件;

  • 当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件。

额外内存池

​ 额外的内存池通常地被DBA忽略,他们认为该值并不十分重要,事实恰恰相反,该值同样十分重要。在InnoDB存储引擎中,对内存的管理通过一种称为内存堆(heap)的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。例如,分配了缓冲池(innodb_buffer_pool),但是每个缓冲池中的帧缓冲(frame buffer)还有对应的缓冲控制对象(buffer control block),这些对象记录了一些诸如LRU、锁、等待等信息,而这个对象的内存需要从额外内存池中申请。因此,在申请很大的InnoDB缓冲池时,也应该相应地增加这个值。

Checkpoint技术

​ 前面已经讲到了,缓冲池的设计目的为了协调CPU速度与磁盘速度的鸿沟。因此页的操作首先都是在缓冲池中完成的。如果一条DML语句,如Update或Delete改变了页中的记录,那么此时页是脏的,即缓冲池中的页的版本比磁盘的新。数据库需要将新版本的页从缓冲池刷新到磁盘。

​ 倘若每次一个页发生变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的。若热点数据集中在某几个页中,那么数据库的性能将变得非常差。同时,如果在从缓冲池将页的新版本刷新到磁盘时候发生了宕机,那么数据就不能恢复了。为了避免发生数据丢失的问题,当前事务数据库系统普遍采用了Write Ahead Log策略,即当事务提交时,先写重做日志,再修改页。当由于发生宕机时,完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。但是这需要两个前提条件:

  • 缓冲池可以缓存数据库中所有的数据;
  • 重做日志可以无限增大。

​ 对于第一个前提条件,有经验的用户都知道,当数据库刚开始创建时,表中没有任何数据。缓冲池的确可以缓冲所有的数据库文件。然而随着市场的推广,用户的增加,产品越来越受到关注,使用量也越来越大。这时负责后台存储的数据库的容量必定会不断增大。当前3TB的MySQL数据库并不少见,但是3TB的内存却非常少见。因此第一个假设对生产环境应用中的数据库是很难得到保证的。

​ 再来看第二个前提条件:重做日志可以无限增大。也许是可以的,但是这对成本的要求太高,同时不便于运维。DBA或SA不知道什么时候重做日志是否已经接近于磁盘可使用空间的阈值,并且要让存储设备可动态扩展也是需要一定的技巧和设备支持的。

​ 好的,即使上述两个条件都满足,那么还有一个情况需要考虑:宕机后数据库的恢复时间。当数据库运行了几个月甚至几年,这时发生宕机,重新应用重做日志的时间会非常久,此时恢复的代价也会非常大。

因此Checkpoint(检查点)技术的目的是解决以下几个问题:

  • 缩短数据库的恢复时间;

  • 缓冲池不够用时,将脏页刷新到磁盘;

  • 重做日志不可用时,刷新脏页。

​ 当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。故数据库只需要对Checkpoint后的重做日志进行恢复。这样就大大缩短了恢复的时间。

​ 此外,当缓冲池不够用时,根据LRU算法会溢出最少使用的页,若此页为脏页,那么需要强制执行Checkponit,将脏页也就是页的新版本刷新回磁盘。

​ 重做日志出现不可用的情况是因为当前事务数据库系统对重做日志的设计都是循环使用的,并不是让其无限增大的,这从成本以及管理上都是比较困难的。重做日志可以被重用的部分是指这些重做日志已经不再需要,即当数据库宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分可以被覆盖重用。若此时重做日志还需要使用,那么必须强制产生Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。

​ 对于InnoDB存储引擎而言,其是通过LSN(Log Sequence Number)来标记版本的。而LSN是8个字节的数字,其单位是字节。每个页都有LSN,重做日志中也有LSN,Checkpoint也有LSN。可以通过命令SHOW ENGINE INNODB STATUS来观察:

img

​ 在InnoDB存储引擎中,Checkpoint发生的时间、条件以及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎将缓冲池中的脏页刷回到磁盘。不同之处在于每次刷新多少页到磁盘,每次从哪里取脏,以及什么时间触发Checkpoint。在InnoDB存储引擎内部,有两种Checkpoint,分别为:

  • Sharp Checkpoint
  • Fuzzy Checkpoint

​ Sharp Checkpoint发生在数据库关闭时将所有的脏页刷新回磁盘,这是默认的工作方式,即参数innodb_fast_shutdown=1。

​ 但是若数据库在运行时也是用Sharp Checkpoint,那么数据库的可用性就会收到很大的影响。故在InnoDB存储引擎内部使用Fuzzy Checkpoint进行页的刷新,即只刷新一部分脏页,而不是刷新所有的脏页回磁盘。

​ 在InnoDB存储引擎中可能发生如下几种情况的Fuzzy Checkpoint:

  • Master Thread Checkpoint

  • FLUSH_LRU_List Checkpoint

  • Async/Sync Flush Checkpoint

  • Dirty Page too much Checkpoint

​ 对于Master Thread中发生的Checkpoint,差不多以每秒或每10秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘。这个过程是异步的,即此时InnoDB存储引擎可以进行其他的操作,用户查询线程不会阻塞。

​ FLUSH_LRU_List Checkpoint是因为InnoDB存储引擎需要保证LRU列表中需要差不多100多个空闲页可供使用。在InnoDB 1.1.x版本之前,需要检查LRU列表中是否有足够的可用空间操作发生在用户查询线程中,显然会阻塞用户的查询操作。倘若没有100个可用的空闲页,那么InnoDB存储引擎会将LRU列表尾端的页移除。如果这些页中有脏页,那么需要进行Checkpoint,而这些页是来自LRU列表,因此称为FLUSH_LRU_LIST Checkpoint。

​ 而从MySQL 5.6版本,也就是InnoDB 1.2.x版本开始,这个检查放在了一个单独的Page Cleaner线程中进行,并且用户可以通过参数innodb_lru_scan_depth控制LRU列表中可用页的数量,该值默认为1024,如:

img

​ Async/Sync Flush Checkpoint指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的。若将已经写入到重做日志的LSN记为redo_lsn,将已经刷新回磁盘最新页的LSN记为checkpoint_lsn,则可定义为:

​ checkpoint_age = redo_lsn - checkpoint_lsn

​ 再定义以下的变量:

​ async_water_mark = 75% * total_redo_log_file_size

​ sync_water_mark = 90% * total_redo_log_file_size

​ 若每个重做日志文件的大小为1GB,并且定义了两个重做日志文件,则重做日志文件的总大小为2GB。那么async_water_mark=1.5G, sync_water_mark=1.8G。则:

  • 当checkpoint_age < async_water_mark时,不需要刷新任何脏页到磁盘;

  • 当async_water_mark < checkpoint_age < sync_water_mark时触发Async Flush,从Flush列表中刷新足够的脏页回磁盘,使得刷新后满足checkpoint_age < async_water_mark。

  • checkpoint_age > sync_water_mark这种情况一般很少发生,除非设置的重做日志文件太小,并且在进行类似LOAD DATA的BULK INSERT操作。此时出发Sync Flush操作,从Flush列表刷新足够的脏页回磁盘,使得刷新后满足checkpoint_age < async_water_mark。

​ 可见,Async/Sync Flush Checkpoint是为了保证重做日志的循环使用的可用性。在InnoDB 1.2.x版本之前,Async Flush Checkpoint会阻塞发现问题的用户查询线程,而Sync Flush Checkpoint会阻塞所有的用户查询线程,并且等待脏页刷新完成。从InnoDB 1.2.x版本开始——也就是MySQL 5.6版本,这部分的刷新操作同样放入到了单独的Page Cleaner Thread中,故不会阻塞用户查询线程。

​ InnoDB版本提供了查看刷新页是从Flush列表中还是从LRU列表中进行Checkpoint和重做日志产生的Async/Sync Flush次数,如:

img

​ 最后一种Checkpoint的情况是Dirty Page too much,即脏页的数量太多,导致InnoDB存储引擎强制进行Checkpoint。其目的总的来说还是为了保证缓冲池中有足够可用的页。其可由参数innodb_max_dirty_pages_pct控制:

img

innodb_max_dirty_pages_pct 值为75表示,当缓冲池中脏页的数量占据75%时,强制进行Checkpoint,刷新一部分脏页到磁盘。在InnoDB 1.0.x版本之前,该参数的默认值为90,之后的版本都为75。

参考

《MySQL技术内幕 InnoDB存储引擎 第2版》

猜你喜欢

转载自juejin.im/post/7036153885848764452