您的位置:首页 > 数据库

数据库日志文件-- undo log 、redo log、 undo/redo log

2012-05-30 16:25 134 查看

在谈日志文件之前,不得不提事务这一概念。在某种意义上来说,如果没有事务存在,也就没有所谓的日志文件。下面开始了。

一、事务

1 ACID

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作。 通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。


原子性

  (atomicity)

  事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。

一致性

  (consistency)

  事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。

隔离性

  (isolation)

  由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。防止数据丢失

持久性

  (Duration)

  事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。



事务必须原子地执行,即全做或全不做,并且在时间上看起来似乎它是在某个时刻一下子就执行完了一样。保证事务正确执行是事务管理器的工作,这一子系统完成的功能包括:
1. 给日志管理器(下面讲述)发信号,使必需的信息能以“日志记录”的形式存储在日志中。
2. 保证并发执行的事务不会以引入错误的方式相互干扰

事务管理器及其相互作用如图:





事务管理器将关于事务动作的消息传给日志管理器,将关于何时可以或必须将缓冲区拷贝磁盘的消息传递给缓冲区管理器,并传消息给查询处理器使之能执行查询以及其他构成事务的数据库操作。
日志管理器维护日志。它必须同缓冲区管理器打交道,因为日志的空间最初出现在主存缓冲区中,在一定的时候这些缓冲区必须被拷贝到磁盘上。日志和数据一样占用磁盘上的空间,正如上图所示。
最后,该图片给出了恢复管理器。当发生崩溃时,恢复管理器就被激活。它检查日志并在必要时利用日志恢复数据。和平常一样,对磁盘的访问是通过缓冲区管理器进行的。

2 事务的原语操作

我们现在详细考虑事务如何同数据库交互。有3个地址空间,它们在重要的方面相互影响:
三个地址空间

1 保持数据库元素的磁盘块空间

2 缓冲区管理器所管理的虚存或主存地址空间

2 事务的局部地址空间

数据在不同的地址空间之间移动的四种操作原语

INPUT(X): 将包含数据库元素X的磁盘块拷贝到内存缓冲区

READ(X,t): 将数据库元素X拷贝到事务的局部变量t

WRITE(X,t): 将局部变量t的值拷贝到内存缓冲区中的X

OUTPUT(X): 将包含X的缓冲区拷贝回磁盘


二、事务日志

日志保证了数据的持久性和事务的原子性。可以简单的认为日志是一个不断追加日志记录的文件。单条日志记录是一段二进制缓冲区。下面是本文会使用到的几条通用的日志记录:

标示trasaction的开始
标示transcatoin成功提交,所有对数据的修改都已经成功。由于cache的存在,在日志中看到COMMIT并不一定意味着 数据的修改都已经持久化。日志的目的就是保证所有COMMIT的事务的修改在程序程序异常退出的情况下能够保留;所有没有COMMIT 的事务的修改在程序异常退出的情况下都不会保留,就像这些事务根本就没有START一样。
事务T的所有修改都不能保留下来,最终体现出来的东西就像事务T根本就没有START过。

日志的类型主要有:undo log,redo log,undo/redo log。最经常使用的类型为redo log和undo/redo log。 如果系统不出现异常情况, 那么日志是没有必要的,因此在讲述的时候大部分场景都是指系统异常退出重启,在叙述过程中不在重复描述这个场景前提。

日志记录的几种形式
(1) <start T> 事务T 开始
(2)<commit T>事务已经成功提交
注:如果要确保<commit T>日志记录写到磁盘上,则在commit后日志管理器应给出一个刷新日志命令告诉缓冲管理器把以前没有拷贝到磁盘的日志记录强行写到磁盘上
(3)<abort T> 表示事务不能成功完成
T终止,日志管理器要保证事务的更新不出现在磁盘上
如果已经写到磁盘上,则要消除之。


UNDO LOG



日志内容
undo log就是把所有没有COMMIT的事务回滚到事务开始之前的状态(撤销事务在系统崩溃前可能还没有完成的影响来恢复数据库状态),对于已经commit的事务不做任何处理。
因为对于commit的事务不做任何 处理,那么在写COMMIT日志记录之前,事务对数据的修改都已经持久化,如果只有一部分持久化,事务修改的数据会处于不一致状态。 另外,由于需要做undo操作,因此日志记录中必须包含数据修改前的值,单条的undo log形式为,表示在事务T开始运行前, X 的值为v;由于对未commit的事务必须进行undo操作,那么在对数据库的数据进行修改之前,必须先保证事务日志已经持久化, 如果日志没有持久化,并且最后事务没有commit,那么数据就无法回滚到事务开始前的状态。由此可以总结出undo
log必须满足的两条规则:

U1:如果事务T改变了数据库元素X,那么形如<T,X,v>的日志记录必须在X的新值写到磁盘之前写到磁盘中
U2:如果事务已提交,则其COMMIT日志记录必须在事务改变的所有数据库元素先写到磁盘之后写到磁盘中,但应尽快。


简要概括规则U1和U2,与一个事务相关额内容必须按如下顺序写到磁盘:

指明所改变数据库元素的日志记录
改变的数据库元素数据本身
COMMIT日志记录

在这里我们可以看到,每个事务commit之前必须把对数据的修改写到磁盘上,这样会导致性能问题, 因为每次事务都回带来一次数据文件的同步写入,而使用日志的主要目的就是减少磁盘的写操作。 因此undo log存在较大的性能问题,因此在实际中使用并不太多。
使用undo日志恢复
系统重启回放日志,只需要从后往前扫描日志文件,对于所有没有commit的事务按照日志记录中的数据做回滚操作。在扫描过程中,恢复管理器记住所有<COMMIT T>或<ABORT T>记录的事务T。同时在向后扫描过程中,如果它看见记录<T,X,v>,则
1 如果T的COMMIT记录已被扫描到,则什么也不做。T已经提交因而不需要撤销
2 否则,T是一个未完成的事务或一个中止的事务。恢复管理器必须将数据库中X的值改为v,以防万一恰好在系统崩溃前X已经被修改了。

CheckPoint (检查点)
按照上述方法回放日志,需要扫描所有日志文件,并且日志文件不能删除。但是实践上我们可以看到, 如果一个事务已经commit了那么之前的 日志记录其实就可以不回放了。因此引入checkpoint的概念。
最简单的checkpoint做法是在做checkpoint的时候阻塞所有更新,直到所有未决的事务都commi或者abort, 然后记录。在日志回放的时候, 如果碰到就停止回放日志。阻塞更新当然不是优雅的处理方式。 下面介绍一种不阻塞更新的checkpoint方法。
生成checkpoint的过程为:

记录START_CKPT<T1,T2,...,Tn>,其中Ti表示开始生成checkpoint的时候未决的事务(没有提交的事务)
等待所有事务提交
记录END_CKPT

日志回放的时候,如果首先遇到END_CKPT,只需要回放日志到下一个START_CKPT为止,START_CKPT之前的日志可以丢弃;如果首先碰到的是START_CKPT, 只需要回放第一步中记录的所有事务最早开始的地方,再之前的日志记录可以直接删除。

REDO LOG




redo log是指在回放日志的时候把已经commit的事务重做一遍,对于没有commit的事务按照abort处理。日志回放并不会处理任何没有commit的事务, 因此,在COMMIT日志持久化之前,不能将数据的修改持久化。因为如果数据在COMMIT之前持久化,那么在系统异常退出的情况下,这种部分修改的事务就会处于一种不一致状态。同时,由于重做事务,因此事务日志中必须记录事务修改以后的值。redo log必须满足以下规则:

R1:在修改磁盘上的任何数据库元素X以前,要保证与X的这一修改相关的所有的日志记录,包括更新记录<T,X,v>及<COMMIT>记录,都必须出现在磁盘上。

当使用redo日志时,与一个事务相关的材料写到磁盘的顺序为:

指出被修改元素的日志记录
写COMMIT日志
修改数据库元素自身

使用日志恢复与checkpoint
对于没有checkpoint的日志进行恢复需要:首先从后往前扫描日志:记录所有已经commit的事务,对于没有commit的事务在日志末尾追加abort; 然后从前往后扫描日志,redo所有已经commit的事务日志。
redo log生成checkpoint的方法:

写日志START_CKPT<T1, T2, ..., Tn>;其中Ti表示当前正在进行还没有commit的事务
当START_CKPT记录写入日志,把所有已提交事务在缓冲区的新值写到磁盘上去。
写日志END_CKPT

在从后往前扫描日志文件的时候,
当然,可能通过特定的方式将一个事务的所有日志串接起来,减少日志的扫描数量。




UNDO/REDO LOG

单独使用的Undo/Redo日志的缺陷

Undo 日志要求数据在事务结束后立即写到磁盘,可能增加磁盘I/O数

Redo日志要求我们在事务提交和日志记录刷新以前将所有修改过的块保留在缓冲区中,可能增加事务需要的平均缓冲区数

如果数据库元素不是完整的块或块块集,在检测点处理过程中,Undo日志和Redo日志在如何处理缓冲区上存在矛盾。

而undo/redo log可以很好的解决这些问题。undo/redo log是指在日志回放的时候像undo log那样回滚所有没有commit的事务; redo log一样redo所有已经commit的事务。由于同时要进行redo和undo,因此日志记录中必须同时记录修改前的值和修改后的值。 <T, X, v, w>,表示事务T修改了元素X的内容,修改之前X的值为v,修改之后值为w。同时undo/redo log对于数据和日志的持久化
顺序要求很低:

URl :在由于某个事务T所做改变而修改磁盘上的数据库元素X前,更新记录 <T, X, v, w> 必须出现在磁盘上

就是说,在读数据库进行修改前,必须前必须先写事务更新日志。undo/redo log也被称作write ahead log。
当使用undo/redo日志时,与一个事务相关的材料写到磁盘的顺序为:

记录update日志
更新数据库(不一定持久化,写入缓存中)
在合适的时候写commit日志 (在磁盘上任何数据库元素的修改之前或之后,即第3步可能比第2步可能早,或者可能晚)

使用日志恢复与checkpoint
没有checkpoint的日志进行恢复很简单:首先从后往前遍历所有日志,undo所有没有commit的事务,并追加abort,同时记录所有 已经提交的事务;然后从前往后遍历日志,redo所有已经提交的事务。
redo/undo log的checkpoint方式与redo log相似:

写日志START_CKPT<T1, T2, ..., Tn>;其中Ti表示当前正在进行还没有commit的事务
将所有缓存中的脏数据持久化到磁盘
写日志END_CKPT

与redo log checkpoint的区别是:在第二步,undo/redo log可以将刷新所有的脏缓冲区,包括还没有commit的事务;而 redo log在第二步的时候只能够将所有已经commit的事务的脏数据持久化到磁盘。
在《Database Systems: The Complete Book》一书中,关于undo/redo log的checkpoint有一段这样的描述:
事务在不确定其不会在中止之前不能写入任何值(甚至来呢写到哦主存缓冲区也不允许)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: