再上一篇:9.1什么是 redo?
上一篇:9.2什么是 undo?
主页
下一篇:9.3提交和回滚处理
再下一篇:9.4分析 redo
文章列表

9.2.1 redo 和undo如何协作?

Oracle 9i 10g编程艺术:深入数据库体系结构

在这一节中,我们来看看在不同场景中redo和undo如何协作。例如,我们会讨论处理INSERT时关 于redo和undo生成会发生什么情况,另外如果在不同时间点出现失败,Oracle将如何使用执行信息。
有意思的是,尽管undo信息存储在undo表空间或undo段中,但也会受到redo的保护。换句话说, 会把 undo数据当成是表数据或索引数据一样,对 undo 的修改会生成一些 redo,这些 redo将计入日志。 为什么会这样呢?稍后在讨论系统崩溃时发生的情况时将会解释它,到时你会明白了。将undo数据增加到 undo段中,并像其他部分的数据一样在缓冲区缓存中得到缓存。

INSERT-UPDATE-DELETE 示例场景

作为一个例子,我们将分析对于下面这组语句可能发生什么情况:
insert into t (x,y) values (1,1);
update t set x = x+1 where x = 1;
delete from t where x = 2;

我们会沿着不同的路径完成这个事务,从而得到以下问题的答案:
如果系统在处理这些语句的不同时间点上失败,会发生什么情况? 如果在某个时间点上ROLLBACK,会发生什么情况?
如果成功并COMMIT,会发生什么情况?

1. INSERT

对于第一条INSERT INTO T语句,redo和undo都会生成。所生成的undo信息足以使INSERT“消失 “。INSERT INTO T生成的redo信息则足以让这个插入”再次发生“。
插入发生后,系统状态如图9-1所示。

图9-1 INSERT之后的系统状态
这里缓存了一些已修改的undo块、索引块和表数据块。这些块得到重做日志缓冲区中相应条目的“保 护“。
假想场景:系统现在崩溃
即使系统现在崩溃也没有关系。SGA会被清空,但是我们并不需要SGA里的任何内容。重启动时就好 像这个事务从来没有发生过一样。没有将任何已修改的块刷新输出到磁盘,也没有任何redo刷新输出到磁 盘。我们不需要这些undo或redo信息来实现实例失败恢复。
假想场景:缓冲区缓存现在已满
在这种情况下,DBWR必须留出空间,要把已修改的块从缓存刷新输出。如果是这样,DBWR首先要求 LGWR将保护这些数据库块的redo条目刷新输出。DBWR将任何有修改的块写至磁盘之前,LGWR必须先刷新 输出与这些块相关的redo信息。这是有道理的——如果我们要刷新输出表T 中已修改的块,但没有刷新输 出与undo块关联的redo条目,倘若系统失败了,此时就会有一个已修改的表T 块,而没有与之相关的redo 信息。在写出这些块之前需要先刷新输出重做日志缓存区,这样就能重做(重做)所有必要的修改,将SGA 放回到现在的状态,从而能发生回滚。
从第二个场景还可以预见到一些情况。这里描述的条件是“如果刷新输出了表T的块,但没有刷新输 出undo块的相应redo,而且此时系统失败了“,这个条件开始变得有些复杂。随着增加更多用户、更多 的对象,再加上并发处理等因素,条件还会更复杂。
此时的情况如图9-1所示。我们生成了一些已修改的表和索引块。这些块有一些与之关联的undo段 块,这3类块都会生成redo来保护自己。如果还记得第4 章中对重做日志缓冲区的讨论,应该知道,它会 在以下情况刷新输出:每3秒一次;缓冲区1/3满时或者包含了1MB的缓冲数据;或者是只要发生提交就 会刷新输出。重做日志缓冲区还有可能会在处理期间的某一点上刷新输出。在这种情况下,其状态如图9-2 所示。

图9-2 重做日志缓冲区刷新输出后的系统状态

2. UPDATE

UPDATE所带来的工作与INSERT大体一样。不过UPDATE生成的undo量更大;由于存在更新,所以需 要保存一些“前“映像。系统状态如图9-3所示。

图9-3 UPDATE后的系统状态
块缓冲区缓存中会有更多新的undo段块。为了撤销这个更新,如果必要,已修改的数据库表和索引 块也会放在缓存中。我们还生成了更多的重做日志缓存区条目。下面假设前面的插入语句生成了一些重做 日志,其中有些重做日志已经刷新输出到磁盘上,有些还放在缓存中。
假想场景:系统现在崩溃
启动时,Oracle会读取重做日志,发现针对这个事务的一些重做日志条目。给定系统的当前状态, 利用重做日志文件中对应插入的redo条目,并利用仍在缓冲区中对应插入的redo信息,Oracle会“前滚” 插入。最后到与图9-1类似的状态。现在有一些undo块(用以撤销插入)、已修改的表块(刚插入后的状 态),以及已修改的索引块(刚插入后的状态)。由于系统正在进行崩溃恢复,而且我们的会话还不再连接
(这是当然),Oracle发现这个事务从未提交,因此会将其回滚。它取刚刚在缓冲区缓存中前滚得到的undo, 并将这些undo应用到数据和索引块,使数据和索引块“恢复”为插入发生前的样子。现在一切都回到从前 。 磁盘上的块可能会反映前面的INSERT,也可能不反映(这取决于在崩溃前是否已经将块刷新输出)。如果 磁盘上的块确实反映了插入,而实际上现在插入已经被撤销,当从缓冲区缓存刷新输出块时,数据文件就 会反映出插入已撤销。如果磁盘上的块本来就没有反映前面的插入,就不用去管它——这些块以后肯定会 被覆盖。
这个场景涵盖了崩溃恢复的基本细节。系统将其作为一个两步的过程来完成。首先前滚,把系统放到 失败点上,然后回滚尚未提交的所有工作。这个动作会再次同步数据文件。它会重放已经进行的工作,并 撤销尚未完成的所有工作。
假想场景:应用回滚事务
此时,Oracle会发现这个事务的undo信息可能在缓存的undo段块中(基本上是这样),也可能已经 刷新输出到磁盘上(对于非常大的事务,就往往是这种情况)。它会把undo信息应用到缓冲区缓存中的数 据和索引块上,或者倘若数据和索引块已经不在缓存中,则要从磁盘将数据和索引块读入缓存,再对其应 用undo。这些块会恢复为其原来的行值,并刷新输出到数据文件。
这个场景比系统崩溃更常见。需要指出,有一点很有用:回滚过程中从不涉及重做日志。只有恢复和 归档时会当前重做日志。这对于调优是一个很重要的概念:重做日志是用来写的(而不是用于读)。Oracle 不会在正常的处理中读取重做日志。只要你有足够的设备,使得ARCH读文件时,LGWR能写到另一个不同
的设备,那么就不存在重做日志竞争。许多其他的数据库(非Oracle)都把日志文件处理为“事务日志”。 这些数据库没有把redo和undo分开。对于这些系统,回滚可能是灾难性的,回滚进程必须读取日志,而 日志写入器正在试图写这个日志。这就向系统中最薄弱的环节引入了竞争。Oracle的目标是:可以顺序地 写日志,而且在写日志时别人不会读日志。

3. DELETE

同样,DELETE会生成undo,块将被修改,并把redo发送到重做日志缓冲区。这与前面没有太大的不 同。实际上,它与UPDATE如此类似,所以我们不再啰嗦,直接来介绍COMMIT。

4. COMMIT

我们已经看到了多种失败场景和不同的路径,现在终于到COMMIT了。在此,Oracle会把重做日志缓 冲区刷新输出到磁盘,系统状态如图9-4所示。

图9-4 COMMIT后的系统状态 已修改的块放在缓冲区缓存中;可能有一些块已经刷新输出到磁盘上。重做这个事务所需的全部redo
都安全地存放在磁盘上,现在修改已经是永久的了。如果从数据文件直接读取数据,可能会看到块还是事 务发生前的样子,因为很有可能DBWR还没有(从缓冲区缓存)写出这些块。这没有关系,如果出现失败, 可以利用重做日志文件来得到最新的块。undo信息会一直存在,除非undo段回绕重用这些undo块。如果 某些对象受到影响,Oracle会使用这个undo信息为需要这些对象的会话提供对象的一致读。