• 正文
  • 相关推荐
申请入驻 产业图谱

数据库内核Checkpoint机制揭秘

08/11 08:29
225
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

假设在崩溃恢复时需要扫描所有的日志,但这是很费时的。例如,仅适用Undo日志,一旦事务提交记录被写到磁盘上,该事务的Undo Log在崩溃恢复时就不再需要了,这可以引发一些思考,设想如果在事务提交后便删除该日志会发生什么?由于数据库是个并发的系统,直接截断日志可能导致活跃事务的Undo Log丢失。其实Checkpoint的目的是缩短崩溃恢复时需要扫描的日志量,具体做法是产生一个LSN,将其称为checkpoint LSN,使得:LSN<checkpoint LSN的事务日志产生的修改已经持久化(脏页已刷写入磁盘,表示这部分事务日志可以删除)。

数据库崩溃恢复时,不需要重做所有的Redo Log:因为Checkpoint之前的脏页都已经刷写到磁盘,只需对checkpoint LSN后的Redo Log日志记录进行恢复即可。

Checkpoint有两种设计,同步的sync checkpoint和异步的fuzzy checkpoint。先说一下同步的sync checkpoint。它的设计思想很简单,就是需要停止写。把Buffer Pool里的脏页全都同步的写回到磁盘,然后把脏页最大的page lsn作为checkpoint LSN。显然,该checkpoint LSN之前日志对应的脏页都已落盘。

再说一下异步的fuzzy checkpoint,它并不需要停止写,Buffer Pool中有一个Flush List。当产生脏页后,把Redo Log的LSN标记到脏页上(即Page LSN)并把脏页都添加到Flush List上。此时需要保证Flush List中的脏页按照Page LSN升序排列。

Flush List上的脏页不再需要同步的写回磁盘,而是由单独的线程page cleaner线程异步地写回, checkpoint LSN只需要取Flush List上第一个脏页的Page LSN即可。这样便能保证该Page LSN之前的日志对应的脏页并不在Flush List上(Flush List中的脏页按照Page LSN升序排列),即已落盘。

下面先看一下MySQL 8.0版本之前关于Checkpoint的实现(即fuzzy checkpoint)。

log_checkpoint(/*===========*/    ibool   sync,       /*!< in: TRUE if synchronous operation is                desired */    ibool   write_always)   /*!< in: the function normally checks if the                the new checkpoint would have a greater                lsn than the previous one: if not, then no                physical write is done; by setting this                parameter TRUE, a physical write will always be                made to log files */{    ...    // 事务执行期间,会把脏页逐渐插入到 buffer pool 的 flush list 上,在事务提交前可能部    // 分落盘(steal),但不要求全部必须落盘(no-force)    // flush list 上的每个脏页都有一个 oldest modified lsn,表示修改过这个页的最早 LSN    //(页载入 buffer pool 后第一次修改该页的 redo log 日志记录)    // 遍历 flush list,找到 flush list 上第一个脏页(最小)的 oldest modified lsn    oldest_lsn = log_buf_pool_get_oldest_modification();         // log_write_up_to(lsn_t lsn, ulint wait, ibool flush_to_disk) 函数是保证小于    // LSN 的所有 redo log日志记录都写入到磁盘(WAL)    // 这里要确定一个问题:lsn < oldest modified lsn 的所有日志应用到的数据页是否已经全部    // 持久化?或者说这些日志是否可以删除?    // 因为 oldest modified lsn 表示修改过这个页的最小 LSN,LSN < oldest modified lsn    // 的日志记录修改的脏页没在 flush list 上,表明这个脏页已经在磁盘上    // LSN 大于 oldest modified lsn 的 redo log日志记录对应的脏页有的在 flush list    // 上有的可能在磁盘上(steal 策略)    // 此处为何还要调用log_write_up_to?难道不能保证oldest_lsn之前的日志已全部写盘?    // 因为函数 log_buf_pool_get_oldest_modification 中,如果 flush list 为空,    // oldest_lsn 是 log_sys->lsn(系统当前写到的日志量),并不能保证 log_sys->lsn 之前    // 的日志已全部刷盘    log_write_up_to(oldest_lsn, LOG_WAIT_ALL_GROUPS, TRUE);      log_sys->next_checkpoint_lsn = oldest_lsn;      // 把 log_sys->next_checkpoint_lsn 持久化(第一个日志文件的 Log Ffile Header 中)    // 奇数次的 checkpoint 写到 LOG_CHECKPOINT_1(OS_FILE_LOG_BLOCK_SIZE,512)偏移    // 偶数次的 checkpoint 写到 LOG_CHECKPOINT_2(3*OS_FILE_LOG_BLOCK_SIZE,3*512)    // 偏移处    log_groups_write_checkpoint_info();}

现在我们来思考一个关键的问题:在数据库重启时,如何判断是否需要崩溃恢复?

例如,有时数据库崩溃但可能直接重启即可,不会违反数据库一致性,这时就不需要崩溃恢复的介入。满足以下任意一个原则,都会认为系统的上一次关闭不是正常的关闭:

▪原则一:checkpoint LSN!= Flushed LSN。
▪原则二:在checkpoint LSN之后还有很多的日志。

顺序上是先原则二、再原则一。

重点关注LOG_CHECKPOINT_1和LOG_CHECKPOINT_2这两个域,InnoDB采用轮询方式轮番写入这两个域,并增加LOG_CHECKPOINT_NO(LOG_CHECKPOINT_NO更大的域便是最近一次Checkpoint写入的域)。InnoDB 启动时,如果 ibdata1 不存在,则被认为是数据库的第一次启动。否则,InnoDB在重新启动时都会去判断之前的数据库退出是正常关闭还是意外崩溃。

在正常关闭时(即用户执行shutdown命令,非意外崩溃),会执行一次同步的checkpoint。除了把checkpoint LSN写入到Redo Log日志文件中(LOG_CHECKPOINT_1 或 LOG_CHECKPOINT_2),还会将checkpoint LSN写入系统表空间的第一个页,偏移是FIL_PAGE_FILE_FLUSH_LSN,被称为Flushed LSN。下面总结一下:

1)正常关闭时,checkpoint LSN和Flushed LSN一定相等。

2)意外崩溃时:checkpoint LSN和Flushed LSN可能相等也可能不相等。例如,正常关闭后再启动,做了一次Checkpoint然后崩溃,那么checkpoint LSN不等于Flushed LSN。或者数据库正常关闭之后再启动,写入了一些日志但还尚未做Checkpoint然后崩溃了,虽然checkpoint LSN等于Flushed LSN,但这种情况明显是需要做崩溃恢复的。这就需要原则二。判断这两个原则是否完备,考虑数据库经历一次正常关闭,再启动后遭遇崩溃有如下4种可能:

情况1:启动之后没有产生日志就崩溃,如图a所示。

情况2:启动之后产生一些日志,还没有做Checkpoint就崩溃,如图b所示。

情况3:启动之后做过Checkpoint,再之后产生一些日志,下一次Checkpoint之前崩溃,如图c所示。

情况4:启动之后做过Checkpoint,再之后没有产生日志就崩溃,如图d所示。

图 崩溃恢复4种情况图示

逐个情况讨论一下:

1)情况1不需要进行崩溃恢复。

2)情况2、情况3由原则二检测到需要崩溃恢复。

3)情况4由原则一检测到需要崩溃恢复。但不需要Redo阶段,仅需要Undo阶段。

为何情况4可能需要回滚?做Checkpoint后不再有Redo Log说明脏页已全部写回磁盘(此时的Flush LSN一定为空),那么可能存在一个事务,当前修改的脏页已经全部在磁盘中,但尚未提交,因此需要Undo阶段。

 

以上内容节选自《InnoDB 核心指南:并发控制和崩溃恢复》作者:吴昊

推荐阅读

▊《InnoDB 核心指南:并发控制和崩溃恢复》,吴昊

本书是一部深入解析数据库内核技术的专业著作,系统阐述了InnoDB存储引擎在并发控制与崩溃恢复两大核心模块的设计理念,从可串行化理论基础到两阶段锁、MVCC等关键协议均进行了细致解读,并结合MySQL 8.0源码实现进行了实证分析。

撰  稿  人:计旭

责任编辑:张淑谦

审  核  人:曹新宇

相关推荐