再读simpledb 之 事务管理的实现(2)

2、恢复管理

2.1 日志的语义实现

前面在Log底层实现提到,Log只负责日志在字节粒度上的实现,并不知晓日志的语义信息。日志的语义信息由恢复管理器来实现。

image_thumb[21]

图1 恢复管理器下日志记录类图

LogRecord作为一个抽象了,定义了所有日志记录的基本接口;系统提供了6种不同的日志类型{CHECKPOINT,START,COMMIT,ROLLBACK,SETINT,SETSTRING}。其中SETINT和SETSTRING是真正向日志文件中写入查询操作等相关的数据,而CHECKPOINT,START,COMMIT,ROLLBACK则是为数据库恢复,向日志文件中加入的辅助信息。

这里着重说下LogRecord中的undo。

以SetInt为例,看下实现的代码:

image_thumb[23]

图2 SetInt下undo方法代码

反做,就是用日志记录中保存的旧值,替换替换指定位置的新值。

有两个地方要注意:

1) –1 假LSN 系统在写入数据的时候,同时生成一条日志记录,但是LSN为-1的时候,这条日志记录只能是临时记录,不会保存到磁盘,参见下面的代码。

image

image

image

图3 LSN为-1的日志记录

2) val 这个val在SetIntRecord对象建立的时候被初始化。SetIntRecord的构造函数有两个:

public SetIntRecord(int txnum, Block blk, int offset, int val);// 创建一条新的SetIntRecord记录

public SetIntRecord(BasicLogRecord rec)                            // 通过一条不含语义的字节记录,包装出一个带语义的日志记录

显然,第二个构造函数得到的对象中,val一定是旧值

image

图4 RecoverMgr的SetInt方法

同样,val是旧值。

这样就印证了SetIntRecord中undo的作用,将对应数据复原成旧值,同时不生成新的日志记录。

2.2 恢复算法

标准的ARIES恢复算法:

1) 先写日志;

2) 重做,重现历史,然后反做,撤销未完成事务;

3) 日志记录反做操作,避免反做“反做”的操作;

simpledb实现了一个变种的恢复算法:只反做的恢复算法

算法的一个前提条件是:在事务提交的时候,强行将缓存中的数据写入磁盘 这样在恢复的时候,不需要利用日志完成重做的过程;跳过ARIES算法的重做步骤;也就不需要保存更新过的日志记录的新值

与标准的ARIES算法比较,这样的好处是:a、日志更小; b、恢复更快

坏处是:事务提交变慢了

所以说,这种只反做的恢复算法,适合系统不稳定,经常需要恢复的情境;当恢复很少发生时,优点不明显,而且提交效率太低,也就不适合了。

接下来,通过代码,看下这种算法的实现。

> commit():

image

图5 事务提交

先flushAll,将事务相关的缓存片全部写入磁盘,然后,写入事务提交标记日志记录

> rollback():

image

image

图6 rollback方法

首先将当前事务关联的脏页写入磁盘,然后回滚事务,恢复当前事务的所有操作,在日志文件中添加一条ROLLBACK的记录,并直接刷如磁盘。

通过doRollBack可以看到回滚的实现细节:

从日志文件倒序回溯,只查看当前事务相关的日志记录,直到遇到START停止,否则,反做该记录。

前面提到,六种日志记录中,只有SetIntRecord和SetStringRecord的undo有实际动作,由于OO的多态特性,此处的rec.undo(txnum)在当记录为SETINT和SETSTRING的时候,隐式调用了这两个的undo,以SetIntRecord为例,在这种情况下,用的构造函数是“public SetIntRecord(BasicLogRecord rec)”,就是从日志中读出旧值,然后复位到原来的位置上。

------------特别地说下--------------

通过事务完成数据写入和必要时事务回滚复原数据,这是两个相反又有些对称的操作,这里提前看下事务的写数据操作:

image

图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记录,标记本次恢复操作。本条记录立即写入磁盘。

image

image

图8 recover方法

posted @ 2012-08-30 14:17  郝玉琨  阅读(619)  评论(0编辑  收藏  举报