LLL的数据库培训-28-第二部分—Oracle基础知识培训—第20讲—重做日志(online redo log)与undo日志文件之二深入解析
数据更改,数据库所做的操作:
insert/delete/update 表t或索引——>undo记录表t改变前的数据 ——redo记录undo数据和表t更改操作——表t的数据(提交或未提交)刷新到数据文件中。
思维导图
一、redo概念再次了解
1、什么是redo
重做日志文件( redo log file)对 Oracle数据库来说至关重要,它们是数据库的事务日志。
2、online和archived
Oracle维护着两类重做日志文件:在线(online)重做日志文件和归档(archived)重做日志文件。
这两类重做日志文件都用于恢复,其主要目的是,当出现数据库实例失败或介质失败时,它们就能派上用场:
(1)如果数据库所在主机掉电
--导致实例失败, Oracle会使用在线重做日志将系统恢复到恰好在掉电之前的那个提交点。
(2)如果磁盘驱动器出现故障(这算是介质失败),
--Oracle会使用归档重做日志以及在线重做日志,以及之前做的一个备份,将原在此驱动器上的数据恢复到某个时间点。
(3)如果你“不小心”截除( truncate)了一个表,或者删除了某些重要的信息并提交了这个操作,
--那么你可以先复原( restore)包含这部分数据的一个备份,然后对其应用在线和归档重做日志, --进而从而将数据恢复(recover)到这个“意外”发生前的时间点。
(4)归档重做日志文件实际上就是已填满的“旧在线重做日志文件的副本。
--当数据库写满在线重做日志文件时,ARCn进程会在另一个位置建立它的副本,当然它也可以在本地或者远端服务器上保留多个副本。 --当出现数据库服务器磁盘驱动器损坏或者其他设备故障时,你就可以使用这些归档重做日志文件来执行介质恢复。 --如果因为设备损坏(或其他原因)丢失了某些数据文件,你可以先将这些数据文件以前做的备份先复原(接下来所提的复原操作皆为 restore,用法皆与此相同)出来,然后对其应用归档重做日志文件,这样就能使这些数据文件“赶上”数据库的其他部分。 --归档重做日志文件本质上是数据库的事务历史记录。
3、redo log group(组)
--每个 Oracle数据库都至少有两个在线重做日志组,每个组中至少有一个成员(重做日志文件)这些在线重做日志组会以循环的方式使用。 --Oracle会先写组1中的日志文件,当写到组1中文件的最后时,它将切换到日志文件组2,开始写这个组中的文件。等到日志文件组2写满时, Oracle将会再次切换回日志文件组1。 --(这里我们假设数据库只有两个重做日志文件组;如果数据库有3个重做日志文件组,那 Oracle当然会在写满第2组之后继续写第3个组。)
4、重做日志是数据库区别于传统文件系统的一个主要因素
--数据库之所以成为数据库(而不是文件系统等其他事物),是因为它有自己独有的一些特征,重做日志即事务日志就是其中重要的特性之一。 --重做日志可能是数据库中最重要的恢复结构,不过,如果没有其他部分(如undo段、分布式事务恢复等),单靠重做日志什么也做不了。 --重做日志是数据库区别于传统文件系统的一个主要因素。 --如果 Oracle正在向磁盘写入,而此时突然发生掉电,当系统恢复供电时,我们就能利用在线重做日志,将数据库重新恢复回来。 --归档重做日志则能够让我们从介质失败中恢复数据,如硬盘损坏,或者由于人为错误而导致数据丢失。如果没有重做日志,数据库提供的保护就比文件系统多不了多少。
二、undo概念再次了解
1、什么是undo
从概念上讲,undo正好与redo相对。
(1)你对数据执行修改时,
--数据库会生成undo信息,以便将来需要的时候可以把数据变更回修改之前的状态。
(2)当你执行的事务或语句由于某种原因失败的时候,或者你用一条 ROLLBACK语句请求回滚时,
--Oracle也需要利用这些undo信息将数据恢复到修改前的样子。
2、redo和undo的区别
--redo用于在失败时重放事务(即恢复事务), --undo则用于取消一条语句或一组语句的作用。 --redo是一组日志文件,放在系统磁盘下。 --undo是存储在数据库内部一组特殊的段(segment)中,称为undo段。
3、回滚段和undo段
--“回滚段”(rollback segment)和“undo段”(undo segment),一般被认为是同义词。 --当使用手动方式来管理undo时,DBA会创建“回滚段”。 --当使用自动方式管理undo时,系统将根据需要自动地创建和销毁“undo段”。
4、undo段是逻辑恢复,非物理恢复
通常有人会对undo有一个误解,认为undo会将数据库“物理地”恢复到某个语句或事务之前的样子,但实际上并非如此。
数据库只是“逻辑地”将数据恢复到原来的样子,某些修改会被“逻理辑地”取消,但是数据结构以及数据库块本身在回滚后可能(与事务或语句开始之前的数据块状态)大不相同。
(1)举个例子:
--创建一个表t,因为11g r2后的延迟段创建特性,创建表t后,并没有给此表分配数据空间,也就是不占用segment。 --给表t插入数据,插入前,表t的信息会以undo段方式存储。 --给表t插入数据后,创建了segent空间分配给表t; --插入数据后,不想要这些数据,对操作进行了rollback(回滚); --rollback过程就是用undo进行恢复,但此时表t的segment空间不会被撤销,而是物理存在了。 --undo所做的操作就是和之前操作相反,之前是insert数据,此时恢复操作就是delete操作。
(2)如此设计的原因
--在所有多用户系统中,可能会有数十、数百甚至数千个并发事务。 --数据库的主要功能之一就是协调它们对数据的并发访问。 --我们的事务修改的块,很有可能同时也正被其他的事务修改。 --因此,我们不能简单地将一个块放回到我们的事务开始前的样子,这样会撤销其他人(其他事务)的工作例如,我们的事务执行了一个 INSERT语句,然后(因为可用空间太小)数据库为插入的数据分配了一个新区段(也就是说,导致表所占用的空间变大)。 --这个INSERT执行的时候,我们会得到一个新的块,然后对其格式化,并在其中放上一些数据。 --此时,可能出现另外某个事务,它也向这个块中插入数据。如果要回滚我们的事务,显然我们不能取消对这个块的格式化和空间分配。
(3)undo回滚实际做的操作
因此, Oracle回滚时,它实际上会做与先前“逻辑上”相反的工作。
--对于每个 INSERT, Oracle会做个 DELETE; --对于每个 DELETE, Oracle会执行一个INSERT; --对于每个 UPDATE, Oracle则会执行一个“反UPDATE”,或者执行另一个 UPDATE将数据恢复到修改前的样子。
三、redo和undo如何协作
我们在下述会讨论处理insert/update/delete语句的过程中,redo和undo是怎么生成的。如果在不同时间点出现失败时,Oracle将如何使用这些信息。
1、undo也受redo的保护
尽管undo信息存储在undo表空间和undo段中,但它也会受到redo的保护。
换句话说,数据库会把undo当成是表数据或索引数据一样处理,对undo的修改会生成一些redo,这些redo将记入日志缓冲区进而写到日志文件中。
为什么会这样呢?稍后我们在讨论系统崩溃会发生的情况时你就会明白了。与数据库中的非undo数据类似,undo数据会写入到undo段中,而且也会被放到缓冲区缓存中。
2、示例场景
(1)操作示范:
--创建一个表t
create table t(id number,name varchar2(10); create index t_i on t(id);
--对此表插入数据
insert into t valus(1,'LLL');
--对此表更新数据
update t set name='TTT' where id=1;
--对此表删除数据
delete t where id=1;
(2)对上述操作,提出问题
--如果系统在处理这些语句的不同时间点上失败,会发生什么情况? --如果缓冲区缓存写满会发生什么情况? --如果在某个时间点上 ROLLBACK,会发生什么情况? --如果全部语句执行成功并 COMMIT,会发生什么情况?
3、场景1:INSERT
图28-1
insert into t语句会同时生成redo和undo,它所生成的undo信息足以使insert“消失”,而redo信息则足以让这个insert“再次发生”。
通过上述图,我们可以看到,块缓冲区缓存里面存放着修改完的undo块、索引块和表数据块,所有的这些块重做日志缓冲区中相应条目所“保护”。
(1)假设场景:系统现在崩溃
这个场景中,我们假设系统崩溃发生在 COMMIT命令之前,或者那些redo条目写到磁盘之前。
这时即使系统崩溃也没关系,SGA会被清空,但是我们并不需要SGA里的任何内容。我们的数据库在重启动之后,就好像这个事务从来没有发生过一样。
那些在缓存中被修改的块都没写到磁盘上,并且也没有任何redo写到磁盘。我们不需要这些undo或redo信息进行实例恢复。
如果用户进行commit,肯定会写入redo log中(这是redolog的触发条件)。
--即使此时没有写入数据data文件中,只要写入redo log就能恢复。一旦数据库崩溃,数据库重新启动后把commit后的数据写入到数据文件中。
如果用户不进行commit,达到重做日志触发条件,也会写入redolog中。
--但对于用户而言,用户commit的,才是他最需要的。 --如果用户没有commit的但已经写入到redolog中的,实例启动后,先redo前滚到数据文件中,再通过undo回滚到没有commit的状态,也不影响真实数据)。
(2)假设场景:缓冲区缓存已满
在这种情况下,DBWn必须要在缓存区中腾出一些空间,它会把一些改动过的块从缓存中刷到磁盘上。
{ 触发 dbwr 进程的条件有:
--dbwr 超时,大约 3 秒 --系统中没有多余的空缓冲区来存放数据 --ckpt 进程触发 dbwr
}
这时,DBWn会首先让LGWR把保护着这些已被修改的块的redo条目刷到磁盘(redo log文件)上去,因为数据库将任何被修改的块刷到磁盘(dbf文件)之前,都必须先让LGWR把这些修改的重做信息写到磁盘(redo log文件)上去。
这是有道理的——看下面的这个错误的例子:
如果我们把表T中已修改的块刷到磁盘上(注意不是与这些修改相关的undo块),但没有刷新输出与undo块关联的redo条目,倘若系统失败了,此时我们就会在磁盘上留下一个已被修改的表T块,而没有与之前这个修改对应的undo信息。如此会无法还原未提交的信息,进而无法保证数据一致性。
所以———————数据库真正的做法是:
在将修改过的数据块刷到磁盘之前,必须先把重做日志缓冲区的redo信息写到磁盘上去,这样一旦发生宕机,我们就可以通过这些redo信息来重做所有修改,从而将SGA回放到现在的状态,进而数据库也有相应的undo信息以对未提交的事务进行回滚。
(也就是任何一条修改记录持久化到数据文件的时候,必须先把它对应的redo条目持久化到磁盘文件,以保证这个过程的可逆性。你可能会问,redo不是前滚吗,可逆应该是回滚,怎么跟redo有关系呢?其实是redo段里面包含了undo段的信息)
(3)假设一个错误场景:“如果刷新输出了表T的块,但没有刷新输出undo块的相应redo,然后此时系统崩溃了”。
因为数据库要做的就是,在刷新输出表t的块之前,必须先把重做日志缓冲区的redo信息写到磁盘上去,做完后,才去刷新表T的数据到数据文件中。所以此种方式不存在。
随着增加更多的用户和更多的对象,再加上并发处理等因素,实际情况还会更复杂。
此时的情况如图28-1所示。我们修改了表和索引的数据块,这些被修改的块有与之关联的undo段块,这3类块都会有redo来保护它们。
如果还记得对重做日志缓冲区的讨论,你应该知道,重做日志缓冲区会在以下情况刷数据到磁盘上:至少每3秒一次;缓冲区写满1/3时或者包含了1MB的缓冲数据;或者发出提交或回滚命令。重做日志缓冲区很有可能会在SQL语句的处理期间刷数据到磁盘上去。
在这种情况下,系统状态如图28-2所示也就是说,数据库缓冲区里会有一些被修改过而未提交的数据块,在磁盘的重做日志里也会有一些未提交的与这些修改相关的redo信息,这样的状态对数据库来说是很正常的而且是极为常见的。
图28-2
4、场景2——UPDATE
图28-3
UPDATE的运作与 INSERT大体一样,不过 UPDATE生成的undo量更大;这是由于 UPDATE需要保存数据修改前的映像。
上图中,重做日志文件中的深色方块代表 INSERT生成的redo,而 UPDATE生成的redo现在还在SGA的重做日志缓冲区中,还没有刷到磁盘上。
在这个状态下,我们在数据库块缓冲区中又有了一些新的undo块,以及修改过的表及索引块。
我们的这个 UPDATE语句也生成了一些新的redo放在重做缓冲区中。
现在我们假设之前的 INSERT语句所生成的redo已经刷到重做日志中去,而 UPDATE语句的还放在重做日志缓冲区中。
(1)假想场景:系统现在崩溃
系统崩溃之后,在数据库重启时, Oracle会读取重做日志,此时它就会发现我们的事务相关的重做日志条目。
其实在系统崩溃时,系统所处的状态是:
INSERT语句的redo已经写入到在线重做日志中(这部分redo也会包含 INSERT语句相关的und段),
但是 UPDATE语句的redo还没来得及写入到磁盘中,而依旧在日志缓冲区中(在系统崩溃时这部分redo会丢失)。
这没问题,我们的事务还没有提交,而磁盘上的数据状态就是 UPDATE发生之前的状态。
但是, INSERT语句的redo已经在系统崩溃之前写到磁盘上了,此时 Oracle会利用这些redo信息“前滚”插入,当前滚结束时,系统的状态将与图28-1非常类似,
缓冲区缓存里面有修改过的undo块(用于回滚INSERT)、表块( INSERT后的状态)及索引块( INSERT后的状态)。
由于这时 Oracle正处于自动的实例恢复过程中,它会回滚所有未提交的事务,包括我们刚才的那个 INSERT语句的事务。
当 Oracle回滚我们的这个未提交的 INSERT时,它会首先取到刚才提到的前滚过程中构建的undo数据,然后将这部分undo应用到数据以及索引块,从而使得数据和索引块“恢复”为INSERT发生前的样子。
现在一切都恢复到了 INSERT之前的状态,不过磁盘上的数据块可能是 INSERT之前,也可能是之后的状态(这取决于在崩溃前是否已经将块刷新输出)。
如果磁盘上的块是 INSERT之后的状态那么当下次这个块从缓冲区缓存刷到磁盘时,数据文件就会反映出 INSERT已撤销状态;
如果磁盘上的块是 INSERT之前的状态,那就不用去管它这些块以后肯定还会被覆盖。
这个场景涵盖了实例恢复的基本细节。整个恢复过程是分两步完成的:首先前滚,把系统重放到失败点上,然后回滚所有尚未提交的工作。这个过程会再次同步数据文件。它会重放已经进行的工作,并撤销尚未完成的所有工作。
(2)假想场景:应用回滚事务(rollback)
当应用发出 ROLLBACK命令时, Oracle会去查找这个事务的undo信息,它可能在缓存的undo段的块中(通常是这样),也可能已经刷新输出到磁盘上(对于非常大的事务,就往往是这种情况)。
接下来 Oracle会把undo信息应用到缓冲区缓存中的数据和索引块上,或者倘若数据和索引块已经不在缓存中,则要从磁盘将数据和索引块读入缓存再对其应用undo。这些块会恢复为其原来的状态,并在以后会被刷新输出到数据文件。)
这个场景比系统崩溃更常见。
需要注意,回滚过程中从不会涉及到重做日志,只有恢复和归档时。
这对于调优意义重大:重做日志是用来写的,不是用来读的, Oracle不会在正常才会读取重做日志的处理中读取重做日志。只要你有足够的设备,使得在ARCn读文件时,LGWR能写到另一个不同的设备,就不存在重做日志竞争。
许多其他的数据库( Oracle)都把日志文件处理为“事务日志”,这些数据库没有把redo和undo分开。
对于这些系统,回滚可能是灾难性的,回滚进程必须读取日志,而日志写人器也可能会在同时写这个日志,这就向系统中最薄弱的环节引入了竞争。
Oracle的目标是:可以顺序地写日志,而且在写日志时别人不会读日志。
5、场景3——DELETE
同样, DELETE会生成undo,块将被修改,并把redo发送到重做日志缓冲区,这与前面没有太的不同。
6、场景4——COMMIT
图28-4
当事务提交时,Oracle会把重做日志缓冲区刷新输出到磁盘,系统状态如图28-4所示。
此时已修改的块放在缓冲区缓存中,可能有一些块已经被刷新输出到磁盘上。
重做这个事务所需的全部redo都安全地存放在磁盘上,现在我们所做的修改已经具有持久性了此时如果我们从数据文件直接读取数据,可能会看到块还是事务发生前的样子,因为很有可能DBWn还没有(从缓冲区缓存刷出这些块。
这没有关系,如果出现宕机,那么在实例恢复的过程中,数据库可以利用重做日志文件来将这些块更新为最新状态。数据库会一直保留着undo信息,除非undo段回绕并重用这些undo块。
Oracle会使用这些undo信息为需要这些对象的会话提供一致性读。
四、提交和回滚处理
1、commit前后,数据库在做什么
通常情况下, COMMIT是一个非常快的操作,你可能认为,其时间与事务大小成正比,一个事务越大(换句话说,它影响的数据多),那么commit时间越长,其实不是这样的。
COMMIT需要的时间与事务大小无关。
不论事务有多大, COMIT的响应时间一般都很“稳定,这是因为并没有太多的工作去做,不过它所做的确实至关重要。
这一点相当重要,只有了解了这一点之后我们才能放心地让数据库去执行大型事务。在上一章我们曾经讨论过,许多开发人员会人为地限制事务的大小,把整个任务分为多个工作单元,并以此进行
多次分批提交。他们这样做的本意是为了节省稀有的系统资源,哪知道他们完全是南辕北辙,这么做实际上增加了资源的使用假设我们 COMMIT一个1行的需要X个时间单位,那么我们COMMIT个1000行的 UPDATE操作基本也需要X个时间单位。
但是我们要是在每行 UPDATE之后立即提交,执行1000次,那么需要的时间就是1000×X。
如果我们的事务只在必要时才提交(即逻辑工作单元结束时),不仅应用的性能可以得到提高,还可以减少对数据库共享资源的竞争(日志文件、各种内部闩等)。
Commit的响应时间不会随着事务的大小而改变,这是因为在数据库中执行commit之前,困难的工作都已经做了。
(案例)如下:
(1)数据库数据改变后,commit前的操作
--已经在SGA中生成了undo块; --已经在SGA中生成了已修改数据块; --已经在SGA中生成了对应前两项的redo缓存; --如果前三项比较大并且耗时较长,那这三项中的某些数据可能已经被刷新输出到磁盘; --已经得到了所需的全部锁。
(2)commit后的操作
--执行 COMMIT时,只剩下如下的工作。 --为事务生成一个scN( System Change Number,系统改变号)。 --LGWR将所有未写入磁盘的重做日志条目写至磁盘,并把SCN记录到在线重做日志文件中。这一步就是真正的 COMMIT,如果我们的提交过程都走到了这一步,事务的状态就是已提交。 --此时事务条目会从 VSTRANSACTION中被“删除”,这从另一个方面说明我们的事务已经提交。 --v$LOCK中会记录着我们的会话持有的锁,这些锁都将被释放,而排队等待这些锁的会话都会被唤醒,从而可以继续完成它们的工作。 --如果事务修改的某些块还在缓冲区缓存中, Oracle就会以一种快速的模式访问并“清理”。块清除( block cleanout)是指清除存储在数据库块首部的与锁相关的信息,实际上它是在清除块上的事务信息,这样下一个访问这个块的人就不用再这么做了。这个清理过程不会生成重做日志信息,这样可以省去以后的大量工作。
2、rollback前后,数据库在做什么
从上述看,commit的时间与修改的数据量大小,并不成正比关系(也就是说,修改数据量的大小并不影响一次commit的时间)。
但rollback时间一定是和修改数据了成正比的。
(1)数据库数据改变后,commit前的操作
--已经在SGA中生成了undo块; --已经在SGA中生成了已修改数据块; --已经在SGA中生成了对应前两项的redo缓存; --如果前三项比较大并且耗时较长,那这三项中的某些数据可能已经被刷新输出到磁盘; --已经得到了所需的全部锁。
(2)rollback需要做的操作
--撤销所有修改。Oracle数据库会读取undo段,然后将undo数据应用到数据块以撤销我们的修改,并将相应的undo条目标记为已应用。如果先前插入了一行, ROLLBACK会将其删除;如果更新了一行,回滚就会取消更新;如果删除了一行,回滚将把它再次插入。 --会话持有的所有锁都将被释放,如果有会话在排队等待我们持有的锁,就会被唤醒。
3、commit和rollback对比
--相比之下, COMMIT只是将重做日志缓冲区中剩余的数据刷新输出到磁盘(redolog),它比 ROLLBACK的工作量要小得多。
--但rollback回滚操作的开销很大,因为你花了大量的时间做工作(commit之前),还要花大量的时间(rollback开始后)撤销这些工作。除非不得已,否则不要轻易回滚。
五、redo和undo
1、redo
每次commit时,将数据的修改立即写到online redo中,但是并不一定同时将该数据的修改写到数据文件中。
因为该数据已经提交,但是只存在联机日志文件中,所以在恢复时需要将数据从联机日志文件中找 出来,重新应用一下,使已经更改数据在数据文件中也改过来!
2、undo
在oracle正常运行时,为了提高效率,假如用户还没有commit,但是空闲内存不多时,会由DBWR进程将脏块写入到数据 文件中,以便腾出宝贵的内存供其它进程使用。
这就是需要UNDO的原因。因为还没有发出commit语句,但是oracle的dbwr进程已经将没有提交 的数据写到数据文件中去了。
undo 也是也是datafile, 可能dirty buffer 没有写回到磁盘里面去。
3、rollback
只有先redo apply 成功了,才能保证undo datafile 里面的东西都是正确的,然后才能rollback。
做undo的目的是使系统恢复到系统崩溃前(关机前)的状态,再进行redo是保证系统的一致性.
不做undo,系统就不会知道之前的状态,redo就无从谈起,所以instance crash recovery 的时候总是先rollforward, 再rollback.
六、一张图解释提交流程
insert/delete/update 表t或索引——>undo记录表t改变前的数据 ——redo记录undo数据和表t更改操作——表t的数据(提交或未提交)刷新到数据文件中。
1、更新一个表的列,条件是empno=7788,字段值是sal=3000的,更改为sal=8000;
2、数据库程序从表中获取到empno=7788这个值所在行的数据,其中sal=3000,所在的位置是f4,b32;
3、数据库程序将此块的信息拷贝到缓存buffer cache中。
4、执行更新——execute命令——
5、首先数据库程序在undo中申请一个空间(f4,b51),并把这个块对应的空间信息调入undo的buffer cache中,然后将empno=7788原来的列sal=3000的值记录到此buffer cache中,并记录到undo数据文件中。
6、随后相关进程,将undo 缓存的值记录到redo log buffer中;此时,redo log也将表更改后的信息(empno=7788所在的行中,sal的值改为8000)也记录到redo log buffer中
7、LGWR进程将redo log buffer中的缓存数据写入redo logfile中(这一步才做到了数据的一致性保护,这个过程不管你啥情况,服务器宕机都可以使数据库还原。)。
--如果此后出现了问题,可以根据redo logfile中记录的更改的值,还有undo记录的更改前的值进行恢复。
--如果不commit,数据通过redo前滚后,再通过undo后滚,还原执行前的状态
--如果进行了commit(redo buffer也会记录当前提交状态,记录到redo logfile中,然后才反馈给客户端,commit完成,客户端收到完成信号,这个事务就完成了)
8、7完成后,有如下两个动作,可能在同时或不同时进行
--7完成后,才允许表的更改的数据(提交或未提交的),通过CKPT检查点进程,将update的值刷新到f4,b32这个位置。 --7完成后,ARCn进程可以给redo log进行归档。
上述文档参考如下:
--https://www.topunix.com/post-4132.html --https://www.topunix.com/post-4019.html --https://www.topunix.com/post-4045.html --https://www.topunix.com/post-4087.html --undo构造cr --https://www.topunix.com/post-4030.html --检测点. --https://www.icode9.com/content-4-1011863.html --undo --https://www.topunix.com/post-4104.html --oracle在事务提交前后,做了什么操作