上一篇:Mysql——InnoDB存储引擎Buffer Pool
简介
上一篇说到,用户的修改操作会导致innoDB缓冲池出现脏页,如果说每次修改都需要将脏页刷新到磁盘的话,那么这个开销是非常大的。
还有一点,如果将脏页刷新到磁盘时发生了宕机,那么这个数据就无法恢复了,因此innoDB采用了Write Ahead Log策略,事务提交时,先记录到redo log,再修改页,修改完会先缓存到innoDB缓冲池中,通过后台线程去统一刷新。这样既能保证数据安全,又能很大提升性能。
但是,如果redo log无限增大(假设数据库已经运行了好几年的时间),缓冲池页无限大,那么宕机的时候通过整个redo log来恢复数据,这时整个redo log重放恢复的时间将会非常久。
Checkpoint(检查点)技术就来解决以上问题的。
Checkpoint机制
Checkpoint的特点
- 缩短数据库恢复的时间:
当数据库发生宕机时,不需要重放整个redo log,只需要重放Checkpoint之后的redo log,这样就可以大大缩短恢复时间; - 缓冲池不够用,将脏页刷新到磁盘:
缓冲池不够用时,根据LRU算法会淘汰掉最近最少使用的页,如果该页是脏页的话,会强制执行CheckPoint,将该脏页刷回磁盘; - redo log不可用时,将脏页刷新到磁盘:
由于重做日志不可能无限大,innoDB重做日志的设计按照循环使用规则,已经不再需要的日志可以被重用。因此如果重做日志经过一圈写到当前Checkpoint的位置,导致redo log不可用,将强制执行CheckPoint,将缓冲池的页至少刷新到当前重做日志的位置。
LSN
LSN(Log Sequence Number)用来标记版本,8个字节的数字。重做日志每增加多少个字节,LSN就递增多少。存放位置:
- redo log:每次事务提交,先在log buffer生成LSN,然后刷新到磁盘的redo log,递增LSN;
- 缓冲池中的页:保存两个LSN,
第一次修改时LSN
,最后一次修改时LSN
;每次修改页,递增最后一次修改时LSN
; - Checkpoint:每次将页刷新到磁盘,保存该页的
第一次修改时LSN
;
因此,数据库恢复时,只需要重放redo log的LSN - Checkpoint的LSN之间的操作。
Checkpoint类型
Checkpoint类型分为:
-
Sharp Checkpoint:发生在数据库关闭时将所有的脏页都刷新回磁盘,如果此时数据库脏页非常多,则会很大影响性能;
-
Fuzzy Checkpoint:默认方式,只刷新一部分脏页,不是刷新所有脏页;主要有以下几种情况:
- Master Thread Checkpoint:每秒或者10秒的频率异步刷新缓冲池的脏页到磁盘。(由Page Cleaner Thread完成)
- FLUSH_LRU_LIST Checkpoint:缓冲池不够用时,根据LRU算法会淘汰掉最近最少使用的页,如果该页是脏页的话,会强制执行CheckPoint,将该脏页刷回磁盘(由Page Cleaner Thread完成);
- Async/Sync Flush Checkpoint:重做日志不可用的情况,需要强制从脏页列表中选取一些脏页刷新磁盘到缓存(由Page Cleaner Thread完成)。
假设将已经写到redo log的LSN记为redo_lsn,将已经刷新回磁盘最后一个脏页的LSN记为checkpoint_lsn(严格讲是最后一个脏页的
第一次修改时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; #若total_redo_log_file_size = 2G,则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;
如果sync_water_mark<checkpoint_age,触发Sync Flush,一般这种情况很少发生,除非日志设置特别小,并且在进行类似LOAD DATA的BULK INSERT操作,使得刷新后满足checkpoint_age<async_water_mark;
- Dirty Page too much Checkpoint:即脏页数量太多,导致强制进行Checkpoint。由参数
innodb_max_dirty_pages_pt
来控制,默认75(即75%)。当脏页数量占据75%缓冲池时,刷新一部分脏页到磁盘。(由Page Cleaner Thread完成)
几个重要参数
- Log sequence number:最新的LSN,存放在Log Buffer;
- Log flushed up to:redo log上面的LSN,从Log Buffer刷新到redo log;
- Pages flushed up to:最后一个脏页被刷回磁盘的
最后一次修改时LSN
; - Last checkpoint at:checkpoint LSN,即最后一个脏页被刷回磁盘的
第一次修改时LSN
;
四者之间的关系:Log sequence number >= Log flushed up to >= Pages flushed up to >= Last checkpoint at
。
为什么checkpoint LSN要记录最后一个脏页被刷回磁盘的
第一次修改时LSN
而不是记录最后一个脏页被刷回磁盘的最后一次修改时LSN
?
笔者认为,由于Flush list是来管理脏页列表,通过指针指向LRU的脏页,并按照脏页第一次修改时LSN进行排序。页的刷新顺序也算是按照Flush list排序规则,先刷出最旧的脏页。假设有这么一个场景,页1的LSN第一次被设置为10000,页2的LSN第一次被设置为20000,接着页1的LSN被修改为21000,此时页1被刷到磁盘,页2没有刷到磁盘,那么checkpoint LSN = 21000;而在这之后机器突然间挂了,重启恢复就会从21000开始恢复,就会导致之前页2的数据无法恢复。