1.前言
如果执行一条DML语句,如update或者delete改变了页中的记录,那么此时页是脏的,即缓冲池中的页的版本要比磁盘的新,数据库需要将新的版本的页从缓冲池刷新到磁盘
2.checkpoint技术
倘若每次一个页的变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的,若热点数据集中在某几个页中,那么数据库的性能就会变得非常差。同时,如果在从缓冲池将页的的新版本刷新到磁盘时发生了宕机,那么数据就不能恢复了,为了避免这种情况,当前事务数据库系统普遍都采用了Write Ahead Log策略,即当事务提交时,先写重做日志,再修改页,当由于发生宕机而导致数据丢失时,可以通过重做日志来完成数据的恢复。这也是事务ACID中D(Durability持久性)的要求。
checkpoint的作用:
- 缩短数据库的恢复时间
- 缓冲池不够用时,将脏页刷新到磁盘
- 重做日志不可用是,刷新脏页
当数据库发生宕机时,数据库不需要重做所有的日志,因为checkpoint之前的页都已经刷新回磁盘。故数据库只需要对Checkpoint后的重做日志进行恢复。这样就大大缩短了恢复时间
此外,当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么就需要强制执行checkpiont,将脏页也就是页的新版本刷回磁盘
重做日志出现不可用的情况是因为当前事务数据库系统对重做日志的设计都是循坏使用的,并不是让其无限增大的,这从成本及管理上都是比较困难的,重做日志可以被重用的部分是只这些重做日志已经不再需要,即当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖使用。如此时重做日志还需要使用,那么必须强制产生checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。如下图
同时我们很容易得知, 在innodb中,既有redo log
需要刷盘,还有 数据页
也需要刷盘, redo log
存在的意义主要就是降低对 数据页
刷盘的要求 ** 。在上图中, write pos
表示 redo log
当前记录的 LSN
(逻辑序列号)位置, check point
表示 数据页更改记录 刷盘后对应 redo log
所处的 LSN
(逻辑序列号)位置。write pos
到 check point
之间的部分是 redo log
空着的部分,用于记录新的记录;check point
到 write pos
之间是 redo log
待落盘的数据页更改记录。当 write pos
追上 check point
时,会先推动 check point
向前移动,空出位置再记录新的日志
对于innodb存储引擎而言,其实通过LSN(Log Sequence Number)来标记版本的,而LSN是8字节的数字,其单位是字节,每个页都有LSN,重做日志中也有LSN,checkpoint也有LSN。可以通过show engine innodb status来观察:
LOG --- Log sequence number 4033522 Log flushed up to 4033522 Pages flushed up to 4033522 Last checkpoint at 4033513 0 pending log flushes, 0 pending chkp writes 10685 log i/o's done, 0.00 log i/o's/second
Log sequence number:表示当前redo log(in buffer)中的LSN
log flushed up to : 表示刷到redo log file on disk中的LSN
pages flushed up to :表示已经刷新到磁盘数据页上的LSN
last checkpoint at :上一次检查点所在的位置LSN
在innodb存储引擎中,Checkpoint发生的时间、条件以及脏页的选择等都非常复杂,而checkpoint所做的无非是将缓冲池中的脏页刷回到磁盘,不同之处每次刷多少页到磁盘、每次从哪里取脏页、以及什么时候触发checkpoint.在innodb存储引擎中,有两种checkpoint,分别是:
- sharp checkpoint
- Fuzzy checkpoint
Sharp checkpoint 发生在数据库关闭将所有脏页都刷新到磁盘,这时默认的工作方式,即参数:innodb_fast_shutdown=1
但是若数据库在运行时也使用sharp checkpoint,那么数据库的可用性就会收到很大的影响,股灾innodb存储引擎内部使用Fuzzy checkpoint进行页的刷新,即只是部分刷新脏页,而不是刷新所有的脏页会磁盘
什么时候会发生checkpoint?
- Master Thread checkpoint
- Flush_LRU_LIst checkpoint
- Async/sync Flusn checkpoint
- Dirty page too much Checkpoint
对于Master thread 中发生的checkpoint,差不多以每十秒的速度从缓冲池的脏页列表中刷新一定比例的页到磁盘,这个过程是异步的,即此时innodb存储引擎可以进行其他的操作,用户查询线程不会阻塞。
Flush_LRU_LIST checkpoint 是因为innodb存储引擎保证LRU列表中需要差不多100个空闲页可供使用。在之前innodb1.1.x版本之前,需要检查LRU列表中有足够的可用空间操作发生在用户查询线程中,显然这会阻塞用户的查询操作。倘若没有100个可用空闲页,那么innodb存储引擎会将LRU列表尾端的页移除。如果有脏页的话,那么需要进行checkpoint,而这些来自LRU列表的,因此称为Flush_LRU_List checkpoint.而Mysql 5.6版本,也就是innodb1.2.x版本开始,这个检查被放在了一个单独的page cleaner线程中进行,并且用户可以通过参数innodb_lru_scan_depth控制LRU列表中可用页的数量,该值默认是1024.
root@localhost 17:11: [information_schema]> show variables like '%innodb_lru%'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | innodb_lru_scan_depth | 1024 | +-----------------------+-------+ 1 row in set (0.02 sec)
Async/Sync Flush checkpoint指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页时从脏页列表中选取的,若将已经写入到重做日志的LSNji
记为redo_lsn,将已经刷新回磁盘最新页的LSN记为checkpoint_lsn,则可定义:
checkpoint_age = redo_lsn - checkpoint_lsn
在定义以下变量:
async_water_mark = 75% * total_redo_log_file_size
sysnc_water_mark = 90% * total_redo_log_file_size
若每个重做日志文件的大小为1GB,并且定义了两个重做日志文件,则重做日志文件的总大小为2G,那么async_water_mark=1.5G,sysnc_water_mark=1.8G,则
- 当checkpoint_age < async_water_mark时,不需要刷新任何脏页到磁盘
- 当async_water_mark < checkpoint_age < sync_water_mark时触发Async Flush ,从Flush列表中刷新足够的脏页回磁盘,使得刷新后满足check_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会阻塞所有的用户查询线程,并且等待脏页刷新完成。从innodb1.2.x版本开始--也就是Mysql5.6版本,这部分的刷新操作同样放入到了单独的Page Cleaner Thread 中,故不会阻塞用户查询线程。
Mysql官方版本不能查看刷新页是从Flush列表中还是从LRU列表中进行Checkpoint的,也不知道因为重做日志而产生的Async/Sync Flush的次数,但是innoSQL版本提供了方法,可以通过命令show engine innodb status 来观察,
最后一种就是Dirty Page too much,即脏页的数量太多,导致innodb存储引擎强制进行Checkpoint.其目的总的来说还是为了保证缓冲池中有足够的可用页。其可由参数:
innodb_max_dirty_pages_pct控制:
root@localhost 20:57: [information_schema]> show variables like '%innodb_max_dirty_pages_pct%'; +--------------------------------+-----------+ | Variable_name | Value | +--------------------------------+-----------+ | innodb_max_dirty_pages_pct | 75.000000 | ##表示脏页最大占比为75%