上篇文章提到,MySQL为了数据持久性,引入了redo log。那么在系统奔溃后,到底如何做数据恢复的呢?我们现在已经知道MySQL会定期做checkpoint,也就是更新checkpoint_lsn的值,可以理解为redo日志中,当前最大可以被覆盖的lsn值。
某一时刻下,各重要lsn值对应如下,lsn代表当前已经写入的redo日志量(buffer),flushed_to_disk_lsn代表已经刷盘的日志量,checkpoint_lsn代表当前允许被覆盖的日志量。即得到如下关系:
现在回到系统奔溃后如何恢复。
首先就是要找到恢复的起点,checkpoint_lsn之前的redo日志对应的数据页都已经被刷到磁盘上了,那么自然这部分的数据不需要重新恢复了,所以起点是checkpoint_lsn。
然后就是要找到恢复的终点,我们知道,redo log是以块为单位的,一个block512字节,而且是顺序写入,每个block中的log block header记录了该块的管理信息,其中一个字段LOG_BLOCL_HDR_DATA_LEN属性记录了当前block中使用了多少字节空间,如果这个属性小于512,那么说明后面没有日志了,这个block就是终点。
最后,redo日志如何恢复数据的呢?redo日志有大大小小几十种类型,需要从物理、逻辑层面上展开来说。
物理上,绝大多数日志类型都有种通用结构:
通过物理结构,我们能够定位到数据发生变化的具体位置了。但是实际上数据恢复没有这么简单,除了修改具体内容以外,可能还要修改 PAGE HEADER , PAGE DIRECTORY等等地方的信息,反正就是:一次数据修改,需要更改的地方非常多。
因此,从逻辑层面上说,MySQL并不是简单找到数据位置,然后修改;而是针对每种不同的log类型,调用不同的函数。函数里面封装了一系列操作,通过调用这些函数,才可以将页面恢复成系统奔溃前的样子(不仅包括恢复具体数据,还要恢复管理信息,以及其他关联信息等)。
MySQL的事务有四大特性,其中之一是持久性,这要求系统奔溃后数据可以被正常恢复。为了保证持久性,引入了redo日志。从性能方面考虑,redo日志先写到buffer区,在适当时机再刷盘到file。从存储方面考虑,为了防止log日志越来越大,MySQL采用了循环写入。为了保证循环写入时,不丢失未持久化的数据,又引入了checkponit的概念,通过checkpoint_lsn可以知道那些日志可以被覆盖。系统奔溃后,又可以通过checkpoint_lsn找到需要被重做的日志起点,以及根据页管理信息找到终点,最后根据每条日志类型不同,调用相关函数恢复数据到系统奔溃前的状态。