再读simpledb 之 事务管理的实现(2)
2、恢复管理
2.1 日志的语义实现
前面在Log底层实现提到,Log只负责日志在字节粒度上的实现,并不知晓日志的语义信息。日志的语义信息由恢复管理器来实现。
图1 恢复管理器下日志记录类图
LogRecord作为一个抽象了,定义了所有日志记录的基本接口;系统提供了6种不同的日志类型{CHECKPOINT,START,COMMIT,ROLLBACK,SETINT,SETSTRING}。其中SETINT和SETSTRING是真正向日志文件中写入查询操作等相关的数据,而CHECKPOINT,START,COMMIT,ROLLBACK则是为数据库恢复,向日志文件中加入的辅助信息。
这里着重说下LogRecord中的undo。
以SetInt为例,看下实现的代码:
图2 SetInt下undo方法代码
反做,就是用日志记录中保存的旧值,替换替换指定位置的新值。
有两个地方要注意:
1) –1 假LSN 系统在写入数据的时候,同时生成一条日志记录,但是LSN为-1的时候,这条日志记录只能是临时记录,不会保存到磁盘,参见下面的代码。
图3 LSN为-1的日志记录
2) val 这个val在SetIntRecord对象建立的时候被初始化。SetIntRecord的构造函数有两个:
public SetIntRecord(int txnum, Block blk, int offset, int val);// 创建一条新的SetIntRecord记录
public SetIntRecord(BasicLogRecord rec) // 通过一条不含语义的字节记录,包装出一个带语义的日志记录
显然,第二个构造函数得到的对象中,val一定是旧值
图4 RecoverMgr的SetInt方法
同样,val是旧值。
这样就印证了SetIntRecord中undo的作用,将对应数据复原成旧值,同时不生成新的日志记录。
2.2 恢复算法
标准的ARIES恢复算法:
1) 先写日志;
2) 重做,重现历史,然后反做,撤销未完成事务;
3) 日志记录反做操作,避免反做“反做”的操作;
simpledb实现了一个变种的恢复算法:只反做的恢复算法
算法的一个前提条件是:在事务提交的时候,强行将缓存中的数据写入磁盘 这样在恢复的时候,不需要利用日志完成重做的过程;跳过ARIES算法的重做步骤;也就不需要保存更新过的日志记录的新值
与标准的ARIES算法比较,这样的好处是:a、日志更小; b、恢复更快
坏处是:事务提交变慢了
所以说,这种只反做的恢复算法,适合系统不稳定,经常需要恢复的情境;当恢复很少发生时,优点不明显,而且提交效率太低,也就不适合了。
接下来,通过代码,看下这种算法的实现。
> commit():
图5 事务提交
先flushAll,将事务相关的缓存片全部写入磁盘,然后,写入事务提交标记日志记录
> rollback():
图6 rollback方法
首先将当前事务关联的脏页写入磁盘,然后回滚事务,恢复当前事务的所有操作,在日志文件中添加一条ROLLBACK的记录,并直接刷如磁盘。
通过doRollBack可以看到回滚的实现细节:
从日志文件倒序回溯,只查看当前事务相关的日志记录,直到遇到START停止,否则,反做该记录。
前面提到,六种日志记录中,只有SetIntRecord和SetStringRecord的undo有实际动作,由于OO的多态特性,此处的rec.undo(txnum)在当记录为SETINT和SETSTRING的时候,隐式调用了这两个的undo,以SetIntRecord为例,在这种情况下,用的构造函数是“public SetIntRecord(BasicLogRecord rec)”,就是从日志中读出旧值,然后复位到原来的位置上。
------------特别地说下--------------
通过事务完成数据写入和必要时事务回滚复原数据,这是两个相反又有些对称的操作,这里提前看下事务的写数据操作:
图7 Transaction的SetInt方法
先利用recoveryMgr创建日志,后写入数据;看前面的图4,RecoveryMgr的SetInt方法,实际上,没有用newval做任何的操作,只是把newval要写入的地反原来保存的oldval读出来,保存到日志中。
这与前面SetIntRecord中的undo,读出日志中的oldval,复位到指定位置,覆盖newval,回滚事务,恢复旧状态,正好是相反的一个过程
-----------------------------------------
> recover():
所有的恢复操作,前面提到的只反做的恢复算法,都在这个方法中实现。
将所有脏数据写入磁盘,然后开始恢复:
1) 维护一个已完成事务的列表 finishedTx
2) 倒序扫描日志文件:
a) 如果遇到COMMIT标记,则该日志记录对应事务已经完成,不用反做,将txnum加入finishedTx
b) 如果遇到ROLLBACK标记,则该日志记录对应事务已经反做过,相当于事务什么都没做,不需要再反做,将txnum加入finishedTx
c) 如果遇到CHECKPOINT标记,则表明自前次系统恢复以来,所有的事务或被反做,或被持久化,本次恢复结束
d) 如果日志记录的txnum不再finishedTx列表中,表明该日志记录对应的事务被中断,需要反做
恢复完成后,在日志记录末尾写入一条CHECKPOINT记录,标记本次恢复操作。本条记录立即写入磁盘。
图8 recover方法