数据页首先被读入 Buffer Pool 中,当数据页中的某几条记录被更新或者插入新的记录时,所有的操作都是先在 Buffer Pool 中完成的。

Buffer Pool 中的某个页和磁盘中的某个页在(Space,PageNumber)上是相同的,但是其内容可能是不同的(Buffer Pool 中的记录被更新过了),形成了脏页。

Buffer Pool 的容量是有限的,这样就需要定期将 Buffer Pool 中的脏页刷回磁盘,达到数据的最终一致 ,即通过 Checkpoint 机制来刷新脏页。

Checkpoint 是用来缩短数据库恢复时间的一种技术。为什么这么说呢?

在修改数据时,都是先对 Buffer Pool 中的数据修改,如果在将脏页刷回磁盘之前,数据库发生宕机了,InnoDB 是如何保证数据一致性的?

因为在数据被修改的时候,会将修改日志写入 redo log 中,如果数据库发生了宕机,通过 redo log 就可以实现将更新记录刷回到磁盘,达到数据的最终一致性。

虽然利用 redo log 就可以实现数据的最终一致,但是当数据库运行的时间很长,redo log 变得非常庞大了,数据库的恢复时间就会变得非常漫长,所以 Checkpoint 就用来尽可能的缩短数据库的恢复时间。

LSN(Log Sequence Number)

Checkpoint 是通过 LSN 来实现的。

LSN(Log Sequence Number)是一个字节数。LSN 存在多个地方。

日志(redo log)中的 LSN:

LSN 存在一个初始值,假设 LSN 的初始值是16,我们修改了一条记录了,就会产生日志,假设这条日志占用10个字节,LSN 就会增加10,变成26,再写入20字节的日志,LSN 就变成46个字节,以此类推。LSN 是一个单调递增的一个值。

页中的 LSN:

页中也存在 LSN,表示该页被修改的时候,对应的日志中的 LSN 是多少。

Page 中的 LSN 号存在页的文件头和文件尾,用来在刷新脏页的时候使用。在刷新脏页的时候会使用 NEWEST_MODIFICATION 的数据。在 Flush List 中是根据 OLDEST_MODIFICATION 来排序的,OLDEST_MODIFICATION 越小表示该页更早被更新,它会先被刷新。

为什么需要在 Page 的文件头和文件尾都需要存放 LSN?下一章讲 InnoDB 的 Double Write 机制,你就知道了,主要是为了保证页的完整性。

可以在 information_schema.INNODB_BUFFER_PAGE_LRU 表中查看当前页中 LSN 的数据。

  1. mysql> desc information_schema.INNODB_BUFFER_PAGE_LRU;
  2. +---------------------+---------------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +---------------------+---------------------+------+-----+---------+-------+
  5. | POOL_ID | bigint(21) unsigned | NO | | 0 | |
  6. | LRU_POSITION | bigint(21) unsigned | NO | | 0 | |
  7. | SPACE | bigint(21) unsigned | NO | | 0 | |
  8. | PAGE_NUMBER | bigint(21) unsigned | NO | | 0 | |
  9. | PAGE_TYPE | varchar(64) | YES | | NULL | |
  10. | FLUSH_TYPE | bigint(21) unsigned | NO | | 0 | |
  11. | FIX_COUNT | bigint(21) unsigned | NO | | 0 | |
  12. | IS_HASHED | varchar(3) | YES | | NULL | |
  13. | NEWEST_MODIFICATION | bigint(21) unsigned | NO | | 0 | | -- 该页最近一次(最新)被修改的LSN
  14. | OLDEST_MODIFICATION | bigint(21) unsigned | NO | | 0 | | -- 该页在Buffer Pool中第一次被修改的LSN
  15. | ACCESS_TIME | bigint(21) unsigned | NO | | 0 | |
  16. | TABLE_NAME | varchar(1024) | YES | | NULL | |
  17. | INDEX_NAME | varchar(1024) | YES | | NULL | |
  18. | NUMBER_RECORDS | bigint(21) unsigned | NO | | 0 | |
  19. | DATA_SIZE | bigint(21) unsigned | NO | | 0 | |
  20. | COMPRESSED_SIZE | bigint(21) unsigned | NO | | 0 | |
  21. | COMPRESSED | varchar(3) | YES | | NULL | |
  22. | IO_FIX | varchar(64) | YES | | NULL | |
  23. | IS_OLD | varchar(3) | YES | | NULL | |
  24. | FREE_PAGE_CLOCK | bigint(21) unsigned | NO | | 0 | |
  25. +---------------------+---------------------+------+-----+---------+-------+
  26. 20 rows in set (0.00 sec)

CheckPoint LSN:

每个数据库中也有一个 LSN,表示 最后一个刷新到磁盘的页的 LSN ,表明了该 LSN 之前的数据都刷回到磁盘了,且如果要做恢复操作,也只要从当前这个 Checkpoint LSN 开始恢复。

CheckPoint LSN 写在 redo log 的前 2K 空间中。

所以说 Checkpoint 是用来缩短数据库恢复时间的一种技术,因为如果数据库宕机了,我只需要恢复 Checkpoint LSN 到当前 redo log 中的 LSN。

通过 show engine innodb status 命令可以查看 LSN 数据。

  1. mysql> show engine innodb status\G
  2. -- ----------省略其他输出-------------
  3. ---
  4. LOG
  5. ---
  6. Log sequence number 4005645497 -- 当前内存中最新的LSN
  7. Log flushed up to 4005645497 -- redo刷到磁盘的LSN(不是在内存中的)
  8. Pages flushed up to 4005645497 -- 最后一个刷到磁盘上的页的最新的LSNNEWEST_MODIFICATION
  9. Last checkpoint at 4005645488 -- 最后一个刷到磁盘上的页的第一次被修改时的LSNOLDEST_MODIFICATION
  • Log sequence number 和 Log flushed up 这两个 LSN 可能会不同,运行过程中后者可能会小于前者,因为 redo log 也是先在内存中更新,再刷到磁盘的。
  • Pages flushed up 与 Last checkpoint 其实都指向了最后一个刷新到磁盘的页,只是 Pages flushed up 代表了页中的 NEWEST_MODIFICATION ,而 Last checkpoint 代表了页中的 OLDEST_MODIFICATION 。

Checkpoint 分类

Checkpoint 分为两类: Sharp Checkpoint 和 Fuzzy Checkpoint。

Sharp Checkpoint 会将所有的脏页刷新回磁盘,通常在数据库关闭的时候使用,刷新的时候系统会 hang 住。设置参数 innodb_fast_shutdown={0|1}。

innodb_fast_shutdown 参数有三个值 0,1,2:

  • 0 表示在 InnoDB 关闭的时候,需要 purge all,merge insert buffer,flush dirty pages。这是最慢的一种关闭方式,但是 restart 的时候也是最快的。
  • 1 默认值,表示在 InnoDB 关闭的时候,它不需要 purge all,merge insert buffer,只需要 flush dirty page,在缓冲池中的一些数据脏页会刷新到磁盘。
  • 2 表示在 InnoDB 关闭的时候,它不需要 purge all,merge insert buffer,也不进行 flush dirty page,只将log buffer 里面的日志刷新到日志文件 log files,MySQL 下次启动时,会执行恢复操作。

官方文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_fast_shutdown,貌似也没有说清楚。

Fuzzy Checkpoint 将部分脏页刷新回磁盘,对系统影响较小。它有一个非常重要的参数:innodb_io_capacity。

innodb_io_capacity 表示一次最多刷新脏页的数量,最小限制是 100,默认值是 200。该值的设置与系统的 IOPS 有关,SSD 可以设置在 4000 ~ 8000,SAS 最多设置在 800 多(IOPS 在1000左右)。

如果该值设置的过小,InnoDB 写的性能就没有发挥出来,设置的过大,会把 MySQL hang住,因为它会等着把这些脏页刷回到磁盘。

刷新的位置

  1. Master Thread Checkpoint:从 Flush List 中刷新

  2. FLUSH_LRU_LIST Checkpoint

    1. 从 LRU List 中刷新(即使不在脏页链表中)
      1. 5.5 以前需要保证在 LRU List 尾部要有 100 个空闲页(可替换的页),即刷新一部分数据,保证有 100 个空闲页,不能是脏页。
    2. innodb_lru_scan_depth:每次进行 LRU List 刷新的脏页的数量。
      1. 该参数应用到每个 Buffer Pool 实例,总数即为该值乘以 Buffer Pool 的实例个数,如果超过 innodb_io_capacity 是不合理的。
      2. 建议该值不能超过 innodb_io_capacity / innodb_buffer_pool_instances。
  3. Async/Sync Flush Checkpoint:重做日志重用

  4. Dirty Page too much Checkpoint:innodb_max_dirty_pages_pct 参数控制。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/hdndc8 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。