当向存储系统写一个数据元素时,通常是先写入主存或者缓冲,然后再写入磁盘,如果系统在写入磁盘的时候,系统发生故障,当系统恢复后,需要再次从磁盘中读取此数据元素的时候,并不知道此时磁盘上所保存的数据元素是正确的还是错误的,Redo日志是一种应对此种故障的比较常用的故障恢复策略。为了确保一个数据元素的完整性,还需要借助事务这一概念,对于更新数据一个元素的redo日志,我们将其描述为“在一个事物T中写入数据元素A的新值x”,使用元组<T, A, x>表示。
一个事务的数据元素的更新操作的redo日志,可以分成三个原语
开始事务T <T, A, x> 事务T写入数据元素A的新值x,这里,数据元素可能是一个数据块,一个记录,或者一个关系
提交事务T
第三个原语非常重要,通常认为,只有一个事务具有提交记录,表明这个事务被完整的执行,即,更新数据元素的操作已经执行成功。
在存储系统更新一个数据元素时,为了保证系统能从故障中恢复后,保证数据的完整性和一致性,采用下面的策略写redo日志到文件(非易失存储)。
事务T开始时,向redo日志写一个
记录 当更新数据元素A之前(注意,此时数据元素可能位于磁盘,也可能位于内存中),先向redo日志文件中写一条<T, A, x>记录。
当
写入redo日志后,此更新操作才被反应到内存或者数据元素A的实际存储磁盘之中。
当系统从故障中恢复后,从redo日志文件头部开始需要扫描redo日志,以恢复数据,如果事务T具有
比如,从账户A转50元到账户B,一系列相关的操作和redo日志记录,以一个泳道图的方式,表示如下,
+----------------+-------+-------+----------------+
| OP | M-A | M-B | REDO LOG |
+----------------+-------+-------+----------------+
| | | | <BEGIN T1> | (1)
| READ(A, a) | 200 | | | (2)
| a = a - 50 | | | | (3)
| | | | <T1, A, 150> | (4)
| OUTPUT(A, a) | 150 | | | (5)
| READ(B, b) | | 200 | | (6)
| b = b + 50 | | | | (7)
| | | | <T1, B, 250> | (8)
| OUTPUT(B, b) | | 250 | | (9)
| | | | <COMMIT T1> | (10)
+----------------+-------+-------+----------------+
注:如果崩溃发生在步骤(10)之后,事务T1是完全可以被重做的,重做后,磁盘上的数据正是人们所期望的值。
为了避免恢复数据时,一直从头开始扫描redo日志文件,也为了避免redo日志文件长度永远不停增长,这里引入检查点这一个概念,日志管理器定期的检查redo日志,把日志记录中包含的新值写到其表示磁盘元素所应在的位置,对于系统恢复而言,可以忽略已经反映到磁盘中的事务redo日志记录
在检查redo日志的时候,一种简单的方式是检查点开始的时候,停止redo日志的更新,写入<START CKPT <t1, t2,,, tn>>, 其中,t1, t2,,, tn是当前活跃的事务,等到当前活跃的事务结束后,写入
<BEGIN T1>
<T1, A, 150>
<BEGIN T2>
<START CKPT T1, T2>
<T2, C, 100>
<T2, D, 200>
<COMMIT T1>
<COMMIT T2>
<END CKPT>
<BEGIN T3>
<T3, A, 100>
<COMMIT T3>
有了检查点后,在恢复数据时,不需要从redo日志的头部开始重做所有日志记录,只需要从日志文件的尾部开始向后扫描,直至遇到第一个
这种方式实现简单,但会造成日志管理在检查周期内不能增加新的事务,而影响整个系统的性能,一个在检查点内活动的、执行了很长时间的事务,会导致整个系统长时间不能接受新的事务。因此一种非静态检查点技术--不会阻塞检查点周期内日志更新,更适合实际使用,其遵循下面的规则:
检查点开始的时候,在日志文件中插入记录<START CKPT <t1, t2, ,, tn>>记录,其中,<t1, t2, ,, tn>是当前活动的事务。
在检查点周期内,如果此检查点所包含的活动事务全部提交,往日志文件尾部插入
记录,若没有全部提交,则无需要提交 记录,也无需等待,进入下一个检查点周期 <T1, A, 150>
<START CKPT, T1, T2> (1) <T2, C, 100> <START CKPT, T1, T2, T3> (2) <T2, D, 200> <START CKPT, T2, T3> (3) <T3, A, 100> <START CKPT T2, T3> (4) (5) (6) <T4, B, 100> ...
为了说明非静态检查点的工作方式,我们让非静态检查点频繁的运行,在上面的例子中,检查点(1)开始时,有两个活动的事务,而在其开始后,系统启动了一个新的事务T3,检查点(1)结束后,T1和T2都未结束,根据规则,直接启动检查点(2),此时,有3个活动事务T1、T2和T3,当检查点(3)开始时,事务T1已经提交,因此,检查点(3)只检查事务T2和T3,但检查点(3)内,任何事务都没有提交,直到检查点(5)开始时,检查事务T3,随后系统新增加事务T4,检查点(5)之前,事务T3提交,因此,在日志文件插入
从非静态检查点的redo日志恢复数据时,仍然从日志的尾部开始,向后扫描,在遇到
继续后扫描,直到<START CKPT ... >记录处停止,然后从此处开始,向前重做redo日志
如果没有扫描到
记录,则无法知道哪些事务已经被反映到磁盘中,则从日志文件的开始处,重做所有的日志 如果所有重做操作都是成功的,向日志文件写入一条
记录
采用非静态检查点在恢复数据时会多扫描一些日志,但带来的好处是非常值得在实际应用中采用它。
由于向redo日志文件写redo日志时,可以采用只追加的方式,redo日志除了应对故障外,还可以带来一些额外的好处,当一个事务 的
合并同若干个同时操作一个数据块的更新操作
采用一定的调度算法,按磁盘、磁片、磁道合并磁盘写操作
这 些更新操作只反应到内存而没有反应到磁盘之中的数据,被称为“胀数据”,当“胀数据”被驱赶出缓冲之前,“胀数据”包含对数据元素的更新需要立即被反映到 磁盘之中。如果存储系统设计在内存中维护了一个很大的数据缓冲,读取数据时,先从缓冲中读取,无疑会提高整个系统的性能。当然这里会引起另外一个话题,采用何种算法提高缓冲的命中率。
注:原创研究,若有转载,保留作者名称 @仪山湖