参考资料
《数据库系统设计》第二版,第6章 系统故障对策,P188~P200
WAL
WAL(write ahead log)是基于重做日志恢复的一个先决条件。意思是,只有当相关日志落盘后,对应的数据才能落盘。比如,下面这条insert语句:
begin;
insert into test(id) values(1);
commit;
只有当insert对应的日志<T1,1,insert>落盘之后,这条insert的元组才能落盘。这是为什么?如果,insert元组先于日志落盘,假设在commit之前发生崩溃,并且此时对应日志还未落盘,依据事务的原子性(要么全做,要么全不做),这条inser的元组需要被undo。但是由于日志未落盘,所以就无法对数据进行undo。于是就违反的事务原子性!
所以,对于WAL还有一个更精确的说法,只有当日志中用作undo的部分落盘之后,对应数据才能落盘。
下面会谈到几种不同的日志策略,其中有一种,undo\redo策略,就有undo部分和redo部分。
下面谈到的几种不同的日志策略都必须首先满足WAL的要求,他们的区别只是在于在事务正常结束前,相关数据是否可以落盘。
强调
WAL隐含了一个原则:缓存页面在写盘时需要加排他锁,即缓存页面写盘时,不允许向页面中写入其他数据。这是为什么?因为无法保证新数据的相关日志已经落盘!。除了给缓存页加排他锁,还可以在写盘之前,先将缓存页中的数据拷贝一份,然后将拷贝的数据进行写盘,而原缓存页可以正常写数据。
undo策略
恢复策略
在重启恢复时,忽略所有正常结束的事务,对于未正常结束的事务undo其相关的所有操作。
在此需要做如下几点说明:
正常结束与非正常结束
通过正常的commit或rollback结束事务称为正常结束。如果在commit或rollback之前系统发生了故障,导致事务终止,则是非常正结束。正常commit的事务有对应的<commit log>;正常rollback的事务有对应的<abort log>。非正常结束的事务则没有对应的log。
undo和rollback的区别
这里不对这两个术语做中文翻译。undo和rollback实现的功能都是回滚或撤销之前的操作。但undo是指,在系统重启恢复时,撤销由于系统异常引起的非正常结束事务的相关操作。而rollback是指,在系统正常运行时,撤销由于逻辑错误或者违反数据库约束(如:主键约束)引起事务正常回滚的相关操作,也可能是响应由客户端或上层应用发起rollback指令。
下面会讲到与之对应的redo策略,redo是在重启恢复时,重做正常提交事务中未落盘的数据。
基于undo策略的事务,在正常执行时,有以下一些特性:
-
对于update操作,在日志中只需要记录原始值,因为恢复时只做undo。
-
在满足WAL的前提下,事务中的相关数据,可以在事务结束之前落盘(所谓的steal策略),并且必须在事务结束之前落盘!换句话说,只有在事务中相关数据全部落盘后,事务才能正常结束!
why?
回到undo日志的策略:恢复时忽略所有正常结束的事务,正常结束的事务可以被忽略的一个前提,就是这些事务已经落盘!
undo策略最大的问题就是要求事务结束前相关数据必须落盘,这会极大的增加磁盘I\O。
checkpoint
再来看看,基于undo日志的checkpoint策略。由于常规checkpoint要求在,checkpoint期间禁止接受新事务,所以数据库的具体实现中,都采用fuzzy checkpoint(模糊检测点)。在这里以及后面两种日志策略,我们都只介绍fuzzy checkpoint方案。
基于undo策略的chekpoint流程如下:
- 写入日志<START CKPT(T1,…,Tn)>并刷新日志,其中T1,…,Tn表示检测点开始时的活动事务。
- 等待T1,…,Tn中的事务正常结束(commit或rollback),期间允许其他事务正常开始。
- 当T1,…,Tn都结束后,写入日志记录<END CKPT>并刷新日志。
由于是基于undo log,所以有这样一个大前提:事务提交时,所有相关数据都已经落盘。所以,根据上述步骤,不难发现一个事实:写入<END CKPT>就意味着,<START CKPT(T1,…,Tn)>中的活动事务,以及<START CKPT>之前的事务都已经提交并落盘。所以:
4. 恢复时,如果先发现<END CKPT>,则说明与<END CKPT>对应的<START CKPT>之前的日志都可以不用回放,并且可以丢弃,因为这些日志对应的事务已经提交,相关数据已经落盘!<START CKPT>中的事务也可以忽略。只有在<START CKPT>后开启的事务,才需要对其状态进行检查,undo那些没有正常结束的事务。
5. 恢复时,如果先发现<START CKPT>,<START CKPT>中可能存在未正常结束需要undo的事务。所以检查<START CKPT>中事务的状态,undo那些没有正常结束的事务。
日志回放顺序
1.我们来考虑一个细节问题,基于undo策略,我们在恢复时,是应该从chekpoint处,从前向后回放到日志结束位置。还是应该从日志结束位置,从后向前回放到chekpiont处?
undo策略在恢复时需要根据事务的状态,来判断对应的操作是应该忽略,还是应该undo。所以在获取某事务具体的操作日志之前,我们应该首先知道事务的状态(正常结束或非正常结束)。如果事务是正常结束,那么会在结束前写入commit或abort日志。换句话说commit或abort是写在该事务所有相关操作日志的最后面。所以我们需要从结束位置向前回放,才能先获取到事务状态。
2.针对前面两种情况,在恢复时有什么区别?这两种情况最大的区别在于,对于情况1,<START CKPT>之前的日志都不用看,所以日志回放到<START CKPT>后即可结束恢复。但对于情况2,还需要检测<START CKPT>中事务状态。所以情况2的恢复分为两个阶段:
- 阶段1:从日志末尾逐条回放日志,直到<START CKPT>。
- 阶段2:到达<START CKPT>后,回放日志直到<START CKPT>中最早发起的事务。
针对阶段2,由于我们已经明确只有<START CKPT>中活动事务,才有可能需要进行undo操作,所以可以在日志中记录一个前向指针(在Aries中被称为PreLSN),指向当前事务的前一条操作日志。如此就可以只回放<START CKPT>中活动事务的相关日志。
redo策略
恢复策略
在重启恢复时,忽略所有未正常结束的事务,对于正常结束的事务redo其相关操作。
基于redo策略的事务,在正常执行时,有以下一些特性:
-
对于update操作,在日志中只需要记录新值,因为恢复时只做redo。
-
在满足WAL的前提下,对于未正常结束的事务,事务中相关的数据都不允许落盘(所谓的no-steal)。
why?
回到redo日志的策略:恢复时忽略所有未正常结束的事务,只有当未正常结束的事务都没有落盘的前提下,这些事务才可以在恢复时被忽略。否则就需要对这些数据进行undo操作。
redo策略相当于undo策略的另一个极端(一个要求事务结束前数据不能落盘,一个要求事务结束前数据必须落盘)。redo策略解决了undo策略的I\O问题,但是增加了对buffer pool的需求。
考虑这样这个场景,假设,数据库的buffer pool被设置为1000个page(postgresql的默认配置),现在有一个update语句,其需要更新的数据涉及数据库中1001个物理块。对于前1000个块的更新可以正常进行。当更新第1001个块时,我们首先需要将这个块加载到buffer pool中。此时buffer pool被此次更新的前1000个page所占用,并且由于事务尚未结束,buffer pool中的所有脏页都不能写盘,所以自然也就不能淘汰,如此就会造成更新失败!
checkpoint
基于redo策略的chekpoint流程如下:
- 写入日志<START CKPT(T1,…,Tn)>并刷新日志,其中T1,…,Tn表示检测点开始时的活动事务。
- 将<START CKPT>前所有已结束事务的相关数据写盘。
- 等到步骤2的相关数据全部落盘后(不必等<START CKPT>中的事务结束),写入<END CKPT>。
针对该checkpoint:
- 恢复时,如果先发现<END CKPT>,则说明与<END CKPT>对应的<START CKPT>之前提交的事务都可以忽略,只需要对<START CKPT>中的活动事务以及<START CKPT>之后开启的事务进行判断。
- 恢复时,如果先发现<START CKPT>,则无法判断该<START CKPT>前已提交的事务是否落盘,所有必须继续向后搜索,直到找到一个<END CKPT>,然后再按照情况1处理。
日志回放顺序
这里,我们同样考虑日志的回放顺序。与undo策略一样,redo策略也需要先获取事务的状态,所以需要从后向前回放日志。但与undo不同的是,undo本身是一个逆向操作(比如:insert的undo是delete,update 2->3的undo是update 3->2),所以在从后向前回放日志的同时可以根据日志内容同步执行undo操作。但redo是一个正向操作,其执行顺序需要与事务正常执行顺序一致。所以在获取事务状态的同时,不能做redo操作。必须等到相关事务状态获取完毕后,再从前往后回放日志,redo那些需要redo的操作。(当然为了减少磁盘I\O,在获取事务状态的同时,可以将需要redo的操作缓存到redo-list中,这样后面只需要正向执行redo-list即可)
undo\redo策略
恢复策略
在重启恢复时,对于所有未正常结束的事务undo其相关操作,对于正常结束的事务redo其相关操作。
基于undo\redo策略的事务,在正常执行时,有以下一些特性:
- 对于update操作,在日志中需要同时记录新值和旧值。
- 在满足WAL的前提下,不需要其他额外限制,事务结束前后,相关数据都可以落盘。
相对于redo策略,undo\redo最大的特点是数据可以在事务正常结束前落盘,undo\redo结合了单一undo或redo的特性,很好的解决了undo或redo自身的特点,该策略也是数据库中应用最广泛的策略(Aries就是基于undo\redo策略)。
checkpoint
基于undo\redo策略的chekpoint流程如下:
- 写入日志<START CKPT(T1,…,Tn)>并刷新日志,其中T1,…,Tn表示检测点开始时的活动事务。
- 将当前所有事务的相关数据写盘。
- 等到步骤2的相关数据全部落盘后(不必等<START CKPT>中的事务结束),写入<END CKPT>。
undo\redo的checkpoint策略与redo的十分相似,其主要区别在于步骤2,undo\redo由于可以在事务结束前将数据落盘,所以在步骤2就不用限制只有已结束事务的相关数据才可以落盘。这一定程度的简化了数据库写盘的逻辑。
基于undo\redo策略的恢复流程也与redo策略十分相似,不过需要在redo策略的基础上添加一些步骤:
- 恢复时,如果先发现<END CKPT>,则说明与<END CKPT>对应的<START CKPT>之前提交的事务都可以忽略,只需要对<START CKPT>中的活动事务以及<START CKPT>之后开启的事务进行判断:
- 如果事务正常结束:忽略该事务所有在<END CKPT>之前的操作(即对于正常结束的事务,日志回放到<END CKPT>就可以停止了,因为<END CKPT>之前操作已经落盘)。redo所有在<END CKPT>之后的操作。
- 如果事务未正常结束:undo该事务相关的所有操作。
- 恢复时,如果先发现<START CKPT>,则无法判断该<START CKPT>前已提交的事务是否落盘,所有必须继续向后搜索,直到找到一个<END CKPT>,然后再按照情况1处理。
4221

被折叠的 条评论
为什么被折叠?



