先说commit,rollback以后补充
那么,为什么COMMIT 的响应时间相当“平”,而不论事务大小呢?在数据库中执行COMMIT 之前,困难的工作都已经做了。我们已经修改了数据库中的数据,所以99.9% 的工作都已经完成。例如,已经发生了以下操作:
q 已经在SGA 中生成了undo 块。
q 已经在SGA 中生成了已修改数据块。
q 已经在SGA 中生成了对于前两项的缓存redo 。
q 取决于前三项的大小,以及这些工作花费的时间,前面的每个数据(或某些数据)可能已经刷新输出到磁盘。
q 已经得到了所需的全部锁。
执行COMMIT 时,余下的工作只是:
q 为事务生成一个SCN 。如果你还不熟悉SCN ,起码要知道,SCN 是Oracle 使用的一种简单的计时机制,用于保证事务的顺序,并支持失败恢复。SCN 还用于保证数据库中的读一致性和检查点。可以把SCN 看作一个钟摆,每次有人COMMIT 时,SCN 都会增1.
q LGWR 将所有余下的缓存重做日志条目写到磁盘,并把SCN 记录到在线重做日志文件中。这一步就是真正的COMMIT 。如果出现了这一步,即已经提交。事务条目会从V$TRANSACTION 中“删除”,这说明我们已经提交。
q V$LOCK 中记录这我们的会话持有的锁,这些所都将被释放,而排队等待这些锁的每一个人都会被唤醒,可以继续完成他们的工作。
q 如果事务修改的某些块还在缓冲区缓存中,则会以一种快速的模式访问并“清理” 。块清除(Block cleanout )是指清除存储在数据库块首部的与锁相关的信息。实质上讲,我们在清除块上的事务信息,这样下一个访问这个块的人就不用再这么做了。我们采用一种无需生成重做日志信息的方式来完成块清除,这样可以省去以后的大量工作(在下面的“块清除”一节中将更全面地讨论这个问题)。
可以看到,处理COMMIT 所要做的工作很少。其中耗时最长的操作要算LGWR 执行的活动(一般是这样),因为这些磁盘写是物理磁盘I/O 。不过,这里LGWR 花费的时间并不会太多,之所以能大幅减少这个操作的时间,原因是LGWR 一直在以连续的方式刷新输出重做日志缓冲区的内容。在你工作期间,LGWR 并非缓存这你做的所有工作;实际上,随着你的工作的进行,LGWR 会在后台增量式地刷新输出重做日志缓冲区的内容。这样做是为了避免COMMIT 等待很长时间来一次性刷新输出所有的redo 。
因此,即使我们有一个长时间运行的事务,但在提交之前,它生成的许多缓存重做日志已经刷新输出到磁盘了(而不是全部等到提交时才刷新输出)。这也有不好的一面,COMMIT 时,我们必须等待,直到尚未写出的所有缓存redo 都已经安全写到磁盘上才行。也就是说,对LGWR 的调用是一个同步(synchronous )调用。尽管LGWR 本身可以使用异步I/O 并行地写至日志文件,但是我们的事务会一直等待LGWR 完成所有写操作,并收到数据都已在磁盘上的确认才会返回。
前面我提高过,由于某种原因,我们用的是一个Java 程序而不是PL/SQL ,这个原因就是PL/SQL 提供了提交时优化(commit-time optimization )。我说过,LGWR 是一个同步调用,我们要等待它完成所有写操作。在Oracle 10g Release 1 及以前版本中,除PL/SQL 以外的所有编程语言都是如此。PL/SQL 引擎不同,要认识到直到PL/SQL 例程完成之前,客户并不知道这个PL/SQL 例程中是否发生了COMMIT ,所以PL/SQL 引擎完成的是异步提交。它不会等待LGWR 完成;相反,PL/SQL 引擎会从COMMIT 调用立即返回。不过,等到PL/SQL 例程完成,我们从数据库返回客户时,PL/SQL 例程则要等待LGWR 完成所有尚未完成的COMMIT 。因此,如果在PL/SQL 中提交了100 次,然后返回客户,会发现由于存在这种优化,你只会等待LGWR 一次,而不是100 次。这是不是说可以在PL/SQL 中频繁地提交呢?这是一个很好或者不错的主意吗?不是,绝对不是,在PL/SQ; 中频繁地提交与在其他语言中这样做同样糟糕。指导原则是,应该在逻辑工作单元完成时才提交,而不要在此之前草率地提交。
redo--> undo-->datafile
insert一条记录时,表跟undo的信息都会放进 redo 中,在commit 或之前, redo 的信息会放进硬盘上。 故障时, redo 便可恢复那些已经commit 了的数据。
redo->
每次操作都先记录到redo日志中,当出现实例故障(像断电),导致数据未能更新到数据文件,则数据库重启时须redo,重新把数据更新到数据文件。
undo->
记录更改前的一份copy,但你系统rollback时,把这份copy重新覆盖到原来的数据
redo->记录所有操作,用于恢复(redo records all the database transaction used for recovery)
undo->记录所有的前印象,用于回滚(undo is used to store uncommited data infor used for rollback)
redo->已递交的事务,实例恢复时要写到数据文件去的
undo->未递交的事务.
redo的原因是:每次commit时,将数据的修改立即写到online redo中,但是并不一定同时将该数据的修改写到数据文件中。因为该数据已经提交,但是只存在联机日志文件中,所以在恢复时需要将数据从联机日志文件中找出来,重新应用一下,使已经更改数据在数据文件中也改过来!
undo的原因是:在oracle正常运行时,为了提高效率,加入用户还没有commit,但是空闲内存不多时,会由DBWR进程将脏块写入到数据文件中,以便腾出宝贵的内存供其它进程使用。这就是需要UNDO的原因。因为还没有发出commit语句,但是oracle的dbwr进程已经将没有提交的数据写到数据文件中去了。
undo 也是datafile,可能dirty buffer 没有写回到磁盘里面去。
只有先redo apply 成功了,才能保证undo datafile 里面的东西都是正确的,然后才能rollback 。
做redo的目的是使系统恢复到系统崩溃前(关机前)的状态,再进行undo是保证系统的一致性。
不做redo,系统就不会知道之前的状态,undo就无从谈起 。
所以instance crash recovery 的时候总是先rollforward,再rollback
undo
回退段中的数据是以“回退条目”方式存储。
回退条目=块信息(在事务中发生改动的块的编号)+在事务提交前存储在块中的数据
在每一个回退段中oracle都为其维护一张“事务表”
在事务表中记录着与该回退段中所有回退条目相关的事务编号(事务SCN&回退条目)
redo
重做记录由一组“变更向量”组成。
每个变更变量中记录了事务对数据库中某个块所做的修改。
当用户提交一条commit语句时,LGWR进程会立刻将一条提交记录写入到重做日志文件中,然后再开始写入与该事务相关的重做信息。
事务提交成功后,Oracle将为该事备生成一个系统变更码(SCN)。事务的SCN将同时记录在它的提交记录和重做记录中。
commit 的开销存在两个因素:
A. 显然会增加与数据库的往返通信。如果每个记录都提交,生成的往返通信量就会大得多。
B. 每次提交时,必须等待redo写至磁盘。这会导致“等待”。在这种情况下,等待成为“日志文件同步”(log file sysnc)
提交事务(COMMIT)前完成的工作:
#在SGA区的回退缓存中生成该事务的回退条目。在回退条目中保存有该事务所修改的数据的原始版本。
#在SGA区的重做日志缓存中生成该事务的重做记录。重做记录中记载了该事务对数据块所进行的修改,并且还记载了对回退段中的数据块所进行的修改。缓存中的重做记录有可能在事务提交之前就写入硬盘中。
#在SGA区的数据库缓丰中记录了事务对数据库所进行的修改。这些修改也有可能在事务提交之前就写入硬盘中。
提交事务(COMMIT)时完成的工作:
#在为该事务指定的回退段中的内部事务表内记录下这个事务已经被提交,并且生成一个惟一的SCN记录在内部事务表中,用于惟一标识这个事务。
#LGWR后进进程将SGA区重做日志缓存中的重做记录写入联机重做日志文件。在写入重做日志的同时还将写入该事务的SCN。
#Oracle服务进程释放事务所使用的所有记录锁与表锁。
#Oracle通知用户事务提交完成。
#Oracle将该事务标记为已完成。
注意
在oracle工作期间,LGWR并非缓存着你所做的所有工作;实际上,随着你的工作的进行,LGWR会在后台曾量式地刷新输出重做日志缓冲区的内容到在线重做日志文件中,这样做是为了避免COMMIT等待很长时间来一次性刷新输出所有的redo。直到commit执行,LGWR才将所有余下的缓存重做日志条目写至磁盘,并把SCN记录到在线重做日志文件中。这一部就是真正的COMMIT,此时事务条目会从V$TRANSACTION中删除,这说明我们已经提交。
rollback 回退事务完成的工作:
·Oracle通过使用回退段中的回退条目,撤销事务中所有SQL语句对数据库所做的修改。
·Oracle服务进程释放事务所使用的所有锁
·Oracle通知事务回退成功。
·Oracle将该事务标记为已完成
举个例子:
insert into a(id) values(1);(redo)
这条记录是需要回滚的。
回滚的语句是delete from a where id = 1;(undo)
试想想看。如果没有做insert into a(id) values(1);(redo)
那么delete from a where id = 1;(undo)这句话就没有意义了。
现在看下正确的恢复:
先insert into a(id) values(1);(redo)
然后delete from a where id = 1;(undo)
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/25496585/viewspace-688768/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/25496585/viewspace-688768/