• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

MySQL InnoDB checkpoint

MySQL InnoDB 彭东稳 7年前 (2017-04-25) 22757次浏览 已收录 3个评论

一、Checkpoint简介

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

若每次一个页发生变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的。若热点数据集中在某几个页中,那么数据库的性能将变得非常差。同时,如果在从缓冲池将页的新版本刷新到磁盘时发生了宕机,那么数据就不能恢复了。为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了Write Ahead Log策略,即当事务提交时,先写重做日志(redo log),再修改页。当由于发生宕机而导致数据丢失时,通过重做日志来完成数据的恢复,这也是事务ACID中D(Durability持久性)的要求。

理论上来说,如果MySQL数据库InnoDB存储引擎的Buffer足够大,就不需要将数据本身持久化。将全部的redo log重新执行一遍就可以恢复所有的数据。但是随着时间的积累,Redo Log会变的很大很大。如果每次都从第一条记录开始恢复,恢复的过程就会很慢,从而无法被容忍。为了减少宕机恢复的时间,就引入了Checkpoint机制,当然这只是其中一个原因,关于Checkpoint具体作用后面有详细介绍。在说Checkpoint之前先了解脏页跟日志顺序号。

  • 脏页(Dirty Page)

如果一个数据页在内存中修改了,但是还没有刷新到磁盘。这个数据页就称作脏页。

  • 日志顺序号(Log Sequence Number)

LSN表示重做日志的序号,8字节的数字,特点是单调递增型,代表每个重做日志的编号,也就意味着每个日志都有一个LSN与其一一对应。另外LSN的增长是使用字节偏移量来表示,就是重做日志每增加多少个字节,LSN就增加多少字节,所以LSN也是事务写入到重做日志的字节总量。

LSN不仅仅记录在重做日志中,它存在于多个对象中,表示的含义各不相同:重做日志(redo log)、页(page)、检查点(checkpoint)。在每个Page头部和尾部都有对应的这个页第一次修改LSN号和最新一次修改LSN号(通过系统视图 information_schema.INNODB_BUFFER_PAGE_LRU 可以看到每个Page第一次修改的LSN(NEWEST_MODIFICATION字段)和最新一次修改的LSN(OLDEST_MODIFICATION字段))。

每个页头部有一个值FIL_PAGE_LSN,该变量用于记录页的LSN,表示该页最后刷新时LSN的大小。因为重做日志记录的是每个页的日志,因此页中的LSN用来判断页是否需要进行恢复操作。例如,页P1的LSN为1000,而数据库启动时,InnoDB检测到写入重做日志中该P1页LSN为1300,并且该事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到P1页中。同样的,对于重做日志中LSN小于P1页的LSN,则不需要进行重做,因为P1页中的LSN表示页已经被刷新到该位置。

Checkpoint(检查点)也通过LSN的形式来保存,其表示页已经刷新到磁盘的LSN位置,当数据库重启时,仅需从检查点开始进行恢复操作。若检查点的LSN与重做日志相同(但一般不会遇见检查点LSN与重做日志LSN相同,一般重做日志LSN比检查点LSN大,具体后面说),表示所有页都已经刷新到磁盘,不需要进行恢复操作了。用Redo Log LSN减去Checkpoint LSN就是需要恢复的数据。如果Checkpoint LSN大于Redo Log LSN那么MySQL就会报错(大概报错就是说到了一个过去的时间)并且Crash数据库。

Checkpoint LSN保存在哪里?

首先我们知道Buffer Pool Instance中一个FLUSH链表,其记录的Buffer Pool Instance中的脏页,是通过指针指向LRU链表具体页,也就是说FLUSH链表本身就是LRU链表的一部分。不过,在FLUSH链表中是根据脏页第一次修改LSN号(oldest)进行排序的。当发生一个脏页时InnoDB会判断是否是第一次修改,如果是就加入到FLUSH链表,如果不是就跳过,避免重复加入。而Checkpoint LSN的获取是每秒钟去FLUSH链表找到最老的页的oldest值记录进Redo日志文件的第一个文件前2k的位置,并且会把这个页刷新到磁盘。也就是说,重做日志LSN大于Checkpoint LSN的部分就是需要恢复的页。

重做日志文件是支持分组的,在每个组中的第一个重做日志文件的前2KB的部分保存了4个512字节大小的块,其存放的内存如下:

需要特别注意的是,上述信息仅在每个重做日志组的第一个重做日志文件中进行存储。重做日志组中的其余日志文件仅保留这些空间,但不实际保存上述信息。而正因为保存了这些信息,意味着,对于重做日志的写入并不是完全顺序的。因为除了log block的写入操作,还需要更新前2KB部分的信息,这些信息对于InnoDB存储引擎的恢复操作来说非常关键和重要。另外,日志文件的2KB后的空间都是按照512字节为一个块用来存储日志信息的。

1. log file header

log file header中保存了对于该重做日志组的一些基本信息,其中LOG_GROUP_ID表示重做日志组的ID号,而LOG_FILE_START_LSN则表示每个重做日志文件第一个日志的LSN。另需要说明一点的是重做日志组在InnoDB中是一个逻辑概念。

2. checkpoint

在log file header后面的部分为InnoDB存储引擎保存的checkpoint值,可以看到有两个checkpoint块,其设计是交替写入的。这样的设计是为了避免介质失败,导致无法找到可用的checkpoint。保存checkpoint的block大小同样为512字节,里面有很多checkpoint相关信息,比较重要的就是有一个LOG_CHECKPOINT_LSN,表示checkpoint LSN的值。

Checkpoint技术主要是解决什么问题?

1. 缩短数据库的恢复时间

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

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

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

3. 重做日志不可用时,刷新脏页

当重做日志出现不可用时,因为当前事务数据库系统对重做日志的设计都是循环使用的(一块连续的磁盘空间),并不是让其无限增大的。重做日志可以被重用的部分是指这些重做日志已经不再需要,即数据页已经被持久化到磁盘上了。当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用。如果重做日志写入过快或其他原因,为了避免数据丢失,那么当重做日志到达某个水平线之时就必须强制Checkpoint,最小也需要保持重做日志的剩余空间保持在这个水平线。

Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint?可以看“MySQL InnoDB Buffer Pool”。

二、Checkpoint工作机制

在InnoDB存储引擎内部,有两种Checkpoint技术,分别为:Sharp CheckpointFuzzy Checkpoint

Sharp Checkpoint发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式,即参数innodb_fast_shutdown=1。但是若数据库在运行时也使用Sharp Checkpoint,那么数据库的可用性就会受到很大的影响,因为一次刷新太多脏页可能会导致数据库被hang住。故在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

Master线程每秒钟从缓冲池的脏页列表中刷新一定比例的页回磁盘,这个过程是异步的,此时InnoDB存储引擎可以进行其他的操作,用户查询线程不会阻塞。

单次刷新的脏页量比较小,可通过调整下面两个参数来控制:

通过capacity能力告知进行刷盘控制,通过Innodb的io能力告知控制对flush list刷脏页数量,io_capacity越高,每次刷盘写入脏页数越多。如果脏页数量过多,刷盘速度很慢,在io能力允许的情况下,调高innodb_io_capacity值,让多刷脏页。但切不可超过IO能力,不然可能会出现卡顿。

  • FLUSH_LRU_LIST Checkpoint

在InnoDB 1.1.x版本之前,因为InnoDB存储引擎需要保证LRU列表中需要有差不多100个空闲页可供使用,需要检查LRU列表中是否有足够的可用空间操作发生在用户查询线程中,显然这会阻塞用户的查询操作。倘若没有100个可用空闲页,那么InnoDB存储引擎会将LRU列表尾端的页移除(因为它是最近最小使用的页)。如果这些页中有脏页,那么需要进行Checkpoint,并且会从FLUSH链表中把这个脏页信息从移除,防止重复刷新。因为这些脏页是来自LRU列表的,因此称为FLUSH_LRU_LIST Checkpoint。

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

此情况下触发,默认扫描1024个LRU冷端数据页,将脏页写入磁盘(有10个就刷10,有100个就刷100个……)。

  • Async/Sync Flush Checkpoint

指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的。为什么会出现不可用呢?因为除了重做日志缓冲在事务提交时需要刷新到磁盘外,其他一些LSN信息都是异步地刷新到持久存储上的。因为即使发生宕机,都可以通过重做日志文件进行恢复。因此存在其他位置的LSN和重做日志LSN产生一定的距离。而这些距离,在实际的应用中可能会非常大。若将已经写入到重做日志的LSN记为redo_lsn,将已经刷新回磁盘最新页的LSN记为checkpoint_lsn,则可以定义:checkpoint_age = redo_lsn – checkpoint_lsn,即未刷新重做日志大小。

再定义以下变量:

若每个重做日志文件的大小为1GB,并且定义了两个重做日志文件,则重做日志文件的总大小为2GB。那么async_water_mark = 1.5GB,sync_water_mark = 1.8GB。则:当checkpoint_age < async_water_mark时,不需要刷新任何脏页到磁盘;当async_water_mark < checkpoint_age < sync_water_mark时触发Async Flush,从缓冲池的Flush列表中刷新足够的脏页回磁盘,使得刷新后满足checkpoint_age < async_water_mark。但是如果log cap继续增加,当checkpoint_age > sync_water_mark时,MySQL会停止事务的更新,此时Redo Log也会停止写入,必须等到刷足够的脏页时,才能允许事务再次提交。本质上说,如果事务提交的速度大于脏页刷盘的速度,最终都会触发上述日志保护的功能,即最终系统停止事务的更新,来保证日志记录的脏页能够刷新到磁盘上。

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中,故不会阻塞用户查询线程。

MySQL官方版本并不能查看刷新页是从Flush列表中还是从LRU列表中进行Checkpoint的,也不知道因为重做日志而产生的Async/Sync Flush的次数。

  • Dirty Page too much Checkpoint

最后,如果脏页的数量太多,也会导致InnoDB存储引擎强制进行Checkpoint,其目的总的来说还是为了保证缓冲池中有足够可用的页。由参数innodb_max_dirty_pages_pct控制:

innodb_max_dirty_pages_pct值为75表示,当缓冲池中脏页的数量占据总页数的75%时,也会强制进行Checkpoint,刷新一部分的脏页到磁盘。在InnoDB 1.0.x版本之前,该参数默认值为90,之后的版本都为75。当然这个机制跟上面说的checkpoint_age机制是不相同的,两者并行。

脏页监控关注点:

Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total:表示脏页在Buffer的占比。

Innodb_buffer_pool_wait_free:如果>0,说明出现性能负载,buffer pool free list中没有可用块(一般低于某个阈值时)。

三、故障恢复机制

InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭与否,都会尝试进行恢复操作。因为重做日志记录的是物理逻辑日志,所以恢复速度比逻辑日志,如二进制日志要快的多。与此同时,InnoDB存储引擎自身也对恢复进行了一定程度的优化,如顺序读取以及并行应用重做日志,这样可以进一步提高数据库恢复的速度。

当MySQL Crash的时候,由于checkpoint表示已经刷新到磁盘页上的日志,因此在恢复过程中仅需恢复checkpoint开始的日志部分。比如当数据库checkpoint LSN为10000时发生宕机,重做日志刷新到的位置为13000,恢复操作仅仅恢复 13000 – 10000 这部分重做日志到Buffer Pool中。

那么这一套LSN运作机制又是什么样的呢?如下图:

MySQL InnoDB checkpoint

如上图所示,Innodb的一条事务日志共经历4个阶段:

  • 创建阶段:当前最新LSN,创建一条Redo日志,此时在Buffer Poll中,可以称之为LSN1。
  • 日志刷盘:记录日志持久化到磁盘上的重做日志文件的LSN,可以称之为LSN2。
  • 数据刷盘:脏页数据刷新到磁盘数据文件时最新的LSN(也就是NEWEST_MODIFICATION),可以称之为LSN3。
  • 写CKP:最后一次检查点对应的LSN,脏页数据刷新到磁盘数据文件时最老的LSN(也就是OLDEST_MODIFICATION),checkpoint LSN就是记录此OLDEST_MODIFICATION值。此检查点之前的脏数据都已经刷新到磁盘,同时也是崩溃恢复时指定的起点,可以称之为LSN4。

注意,LSN1和LSN2和LSN3这三个值一般在服务器没有写操作时都是一致的。LSN3记录页的NEWEST_MODIFICATION,表示页最新一次被修改的LSN;而LSN4记录的是OLDEST_MODIFICATION,表示页第一次被修改的LSN;这两个值都是对同一个页做的记录。所以,这里大部分情况下你都会看到LSN4 < LSN3,如果出现LSN4 = LSN3那么就说明最后刷新的页只有被修改一次。

为什么大部分情况都是LSN4 < LSN3呢?前面已经说了,checkpoint LSN记录的是OLDEST_MODIFICATION,checkpoint每秒钟会去自动获取FLUSH链表中最老的页的oldest值,并且会把这个页刷新到磁盘。也就是说,重做日志LSN大于Checkpoint LSN的部分就是需要恢复的页,小于checkpoint LSN的都表示已经被持久化到磁盘了。

对应这4个阶段,系统记录了4个日志相关的信息,使用 show engine innodb status\G 命令查看:

其中LSN2 – LSN1(Log Buffer日志量)可以表示正在进行的事务,可能是一个事务,也可能组提价下的一组事务。LSN4 – LSN2表示脏页数据,就是缓冲池中还未刷新到磁盘的数据量,也是系统崩溃恢复的数据。一般来说,以上4个LSN是递减的,即: LSN1>=LSN2>=LSN3>=LSN4。

但是这里注意一下。Pages flushed up to 3558502275461 和 Last checkpoint at 3558502275452,这里是一个没有任何操作的库所以 Pages flushed up to 应该和 Last checkpoint at 相等,但是这里存在差值,差值为 9。

这刚好是 MLOG_CHECKPOINT 的长度源码片段如下:

四、重做日志保护机制

MySQL Crash的时候,Innodb有日志刷盘机制,可以通过innodb_flush_log_at_trx_commit参数进行控制,这里说的是如何防止日志覆盖导致日志丢失。

Innodb的checkpoint和redo log有哪些紧密关系?有几个名词需要解释一下:

MySQL InnoDB checkpoint

  • Ckp age(动态移动): 最老的dirty page还没有flush到数据文件,即没有做last checkpoint的范围。
  • Buf age(动态移动): modified page信息没有写到redo log文件中,但已在log buffer。
  • Buf async(固定点): 日志空间大小的7/8,当buf age移动到Buf async点时,强制把没有写到log中的modified page信息开始写入到log中,不阻塞事务。
  • Buf sync(固定点): 日志空间大小的15/16,当写入很大的,buf age移动非常快,一下子到buf sync的点,阻塞事务,强制把modified page信息开始写入到log中。如果不阻塞事务,未做last checkpoint的redo log存在覆盖危险。
  • Ckp async(固定点): 日志空间大小的31/32,当ckp age到达ckp async,强制做last checkpoint,不阻塞事务。
  • Ckp sync(固定点):日志空间大小,当ckp age到达ckp sync,强制做last checkpoint,阻塞事务,存在redo log覆盖的危险。

接下来分析4种情况

  • 如果buf age在buf async和buf sync之间
  • 如果buf age在buf sync之后(当然这种情况是不存在,MySQL有保护机制)
  • 如果ckp age在ckp async和ckp sync之间(这种情况是不存在)
  • 如果ckp age在ckp sync之后(这种情况是不存在)

第一种情况:

MySQL InnoDB checkpoint

当写入量巨大时,buf age移动到buf async和buf sync之间,触发写出到redo log中,MySQL把尽量多的log写出,如果写入量减慢,buf age又移回到“图一”状态。如果写入量大于flush log的速度,buf age最终会和buf sync重叠,这时所有的事务都被阻塞,强制将2*(Buf age-Buf async)的脏页刷盘,这时IO会比较繁忙。

第二种情况:

MySQL InnoDB checkpoint

当然这种情况是不可能出现,因为如果出现,redo log存在覆盖的可能,数据就会丢失。buf age会越过log size,buf age的大小可能就超过log size,如果要刷buf age,那么整个log size都不够容纳所有的buf age。

第三种和第四种情况不存在分析:

ckp age始终位于buf age的后面(左边),因为ckp age是last checkpoint点,总是追赶buf age(将尽可能多的modified page flush到磁盘),所以buf age肯定是先到达到buf sync。

ckp async及ckp sync存在意义?

MySQL中page cache也存在high water及low water,当dirty page触到low water时,os是开始flush dirty page到磁盘,到high water时,会阻塞一切动作,os会疯狂的flush dirty page,磁盘会很忙,存在IO Storm。

<参考>

针对SSD的MySQL IO优化

MySQL checkpoint深入分析

Innodb检查点和redo写盘时机


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (6)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!

(3)个小伙伴在吐槽
  1. NEWEST_MODIFICATION 指的是最新更改的LSN OLDEST_MODIFICATION 指的是第一次更改的LSN
    2017dbawyy2018-08-17 15:18 Windows 7 | Chrome 68.0.3440.106