之前讲到,缓冲池设计的目的就是协调CPU和磁盘之间速度的鸿沟,因此对页的操作首先是在缓冲池里面完成的,也就是如果修改了一个页里面的记录,那么这个页就是脏的,也就是缓冲池里面的页要比磁盘里面的新。因此需要将缓冲池里面的页刷新到磁盘。但是如果每当一个页发生变化就刷新的话,效率也很低。同时,如果将缓冲池的页刷新到磁盘的过程中宕机了,那么数据就不能恢复了。因此事务数据库普遍采用write ahead log,即当事务提交时,先写重做日志(redo log),再修改页。这样即使宕机了,也可以通过redo log来恢复数据。这也是事务ACID里面的D持久性要求。
而使用CheckPoint技术主要是为了解决以下几个问题:
1、减小数据库的恢复时间。如果不使用checkpoint的话,数据库恢复的时候就需要执行redo log的所有内容,可想而知,如果数据库之前执行了几个月,甚至几年,那么这个恢复的时间将会非常久。因此使用CheckPoint机制的话,数据库就不需要重做所有的日志,因为CheckPoint之前的页已经被刷新回磁盘了,只需要对CheckPoint之后的重做日志进行恢复即可。这样就可以缩短恢复时间。这个更具体的过程对应着后面的同步/异步 Flush CheckPoint.
2、因为缓冲池不可以无限增大,当缓冲池不够用的时候,会将脏页刷新回磁盘。具体来说对应着后面的Flush LRU List CheckPoint
3、因为重做日志也不可以无限增大,当重做日志不可用时(实际上就是重做日志可用空间不够时),刷新脏页到磁盘。更具体的过程对应着后面的同步/异步 Flush CheckPoint.
那么CheckPoint主要分为Sharp CheckPoint和Fuzzy CheckPoint。
- Sharp CheckPoin就是在数据库关闭的时候,将所有的脏页刷新回磁盘。
-
而Fuzzy CheckPoint就是只刷新一部分脏页,而不是刷新所有脏页到磁盘。
有以下几种情况可能发生Fuzzy CheckPoint:
【1】如果缓冲池里面的脏页太多,也会导致checkpoint.其目的就是保证缓冲池里面有足够的可用的页。当缓冲池里面脏页超过75%,强制刷新一部分脏页到磁盘。
【2】Master Thread中存在CheckPoint,会以每秒或者每十秒的频率从缓冲池的脏页里面刷新一定比例的页回磁盘。这个过程是异步的,也就是用户感受不到。
【3】在LRU list里面也存在CheckPoint,具体来说因为innoDB存储引擎需要LRU list里面需要大约100个空闲页(也就是说LRU list不能放满),因此如果检查发现LRU list没有100个空闲页,他就会将LRU list尾部的页移除,但是如果这些页里面有脏页,那么需要进行CheckPoint,立即将这些脏页刷新到磁盘。
【4】当redo log可用空间太少的时候,也会导致checkpoint。之前说到,提交修改时是先写到redo log里面,然后再修改页。但是redo log并不能无限增大,redo log的大小是一定的,因此redo log是不断的进行循环利用的,也就是说新的修改会覆盖掉redo log的可覆盖部分,那哪些部分是可覆盖的呢?就是如果数据库宕机之后恢复时,不需要这部分的redo log,他们就是可覆盖的。
此外,我们还需要了解LSN,这个用来标记版本信息,单位是字节,我觉得可以把它理解为某部分数据的字节数。有以下几种LSN:
1、checkpoint_lsn:表示缓冲区里面已经刷新回磁盘的最新页的LSN,表明了该LSN之前的数据都刷回到磁盘了,且如果要做恢复操作,也只要从当前这个 CheckPoint LSN 开始恢复。(可以理解为已经刷新回磁盘的字节数)
2、redo_lsn:重做日志的lsn,也就是日志里面的最新版本(也可以理解为重做日志里面的字节数,因为我们之前会把页保存在redo log里面)
3、我们定义checkpoint_age=redo_lsn-checkpoint_lsn;
4、每个重做日志为1G,一共两个重做日志,加起来的大小为2GB,定义为total_redo_log_file_size。
如果checkpoint_age<0.75*total_redo_log_file_size,那么不需要刷新任何脏页到磁盘。
如果checkpoint_age>0.75*total_redo_log_file_size,那么从Flush list里面刷新足够的脏页回磁盘,使得checkpoint_age<0.75*total_redo_log_file_size。
checkpoint_lsn和redo_lsn之间的关系如下:
1、重做日志LSN = CheckPoint的LSN ,则表示所有页都已经刷回磁盘
2. 重做日志的LSN > CheckPoint的LSN ,则表示还有页没刷到磁盘;如果是宕机,则需要用日志恢复。
可以用图表来表示:
日志中的LSN大部分情况下是大于 CheckPoint的LSN,也就是说 CheckPoint的LSN部分,是我们已经刷新到磁盘的部分,这部分日志留着也没用,因为实际数据已经刷新回磁盘了,因此是可以覆盖的,而剩下的checkpoint_age还没有刷新回磁盘,这部分日志是必须留着的,因此不可覆盖。之前求的checkpoint_age实际就是不可覆盖部分的大小,如果这部分太大的话(根据上面的公式:不可覆盖部分超过75%)就要把缓存里面的一些脏页刷新回磁盘,这样可以增大CheckPoint的LSN,也就是右边部分,从而减小redo log的不可覆盖部分。
那么上面使用这么多LSN的意义是什么呢?
是为了保证redo log 的循环可用,因为如果redo log 的不可覆盖部分太大,后面我们想在redo log里面添加新的内容都无法添加了,因此checkpoint机制可以不断把脏页刷新到磁盘,从而减小redo log的不可覆盖部分,保证可以不断往redo log里面添加数据。