redo/undo
一、什么是redo?
redo:oracle在在线或者归档重做日志文件中的记录的信息,外以出现失败时可以利用这些数据来"重放"事务。
每个oracle数据都至少有二个在线重做日志组,每个组中的至少有一个成员,这些在线重做日志组以循环方式使用。
二、什么是undo?
undo:oracle在undo段中记录的信息,用于取消或者回滚事务。
undo在数据库内部存储在一组特殊的段中,称作undo段。
利用undo段恢复数据,不是将数据库物理地恢复到执行语句或者事务之前的样子,只是从逻辑上恢复到原来的样子,但是数据结构以及数据库块本身在回滚后可能大不相同。oracle在回滚时候,它实际上会做与先前逻辑上相反的工作,对于每个insert,oracle会完成一个delete,对于每个delete,oracle会执行一个insert,对于每个update,oracle则会执行一个"反update",或者执行另外一个update,将修改前的行放回去。
小实验:
create table t as select * from all_objects where 1=0;
select * from t;
set autotrace traceonly statistics
select * from t;
insert into t select * from all_objects;
rollback;
select * from t;
set autotrace traceonly statistics
select * from t;
三、redo和undo如何协作?
undo信息存储在undo表空间或者undo段中,但是也会受到redo的保护。
在dml语句中,redo和undo都会生成。update生成的undo要比insert大,因为update需要保存修改数据的"前"映像。
系统崩溃恢复有两个过程,首先数据前滚,把系统放到失败点上,然后回滚尚未提交的所有工作。这个动作会再次同步数据文件。它会重放已经进行的工作,并撤销尚未完成的所有工作。
oracle有一点很重要:rollback过程从不涉及到redo日志。只有恢复和归档时会读取redo日志。oracle的目标是可以顺序写redo日志,而且在写日志时别人不会读日志 。
四、commit会做什么?
commit通常是一个非常快的操作,而不论事务大小如何。
这是oracle提倡用户的使用事务的提交根据业务来原因之一。commit的开销存在二个因素:
1、增加与数据库的往返通信。
2、每次提交时,必须等待redo写至磁盘,这会导致"等待"。在这种情况下,等待称为"log file sync".
commit前做的工作:
1、已经在SGA中生成了undo块。
2、已经在SGA中生成了已经修改数据块。
3、已经在SGA中生成了对应前二项的缓存redo。
4、取决于前三项的大小,以及这些工作花费的时间,前面的某个数据或者某些数据可能已经刷新输出到磁盘。
5、已经得到了所需的全部锁。
commit时候做的工作:
1、为事务生成一个scn。每次有人commit时,scn都会增1。
2、lgwr将所有余下的缓存重做日志条目写至磁盘,并把scn记录到redo日志文件中。这一步是真正的commit。如果出现了这一步,即已经提交。事务条目会从v$transaction中删除,这说明我们已经提交。
3、v$lock中记录着我们的会话持有的锁,这些锁都将被释放,而排队等待这些锁的每一个人都会唤醒,可以继续完成他们的工作。
4、如果事务修改的某些块还在缓冲区缓存中,则会以一种快速的模式访问并"清理"。块清除是指清除存储在数据块首部的与锁相关的信息。
五、rollback会做什么?
rollback必须逻辑地撤销我们所做的工作,回滚时间绝对是所修改数据量的一个函数。
rollback时候做的工作:
1、撤销已经做的所有修改。其完成方式如下:从undo段读回数据,然后实际上逆向执行前面所做的操作,并将undo条目标记为已用。
2、会话持有的所有锁都将释放,如果有人在排队等待我们持有的锁,就会被唤醒。
六、分析redo
redo管理是数据库中的一个串行点。任何oracle实例都只有一个lgwr,最终所有事务都会归于lgwr,要求这个进程管理他们的redo,并commit其事务。lgwr要做的越多,系统就会越慢。
1、如何测量redo?
v$mystat:会话的统计信息。
v$statname:这个视图告诉我们v$mystat中的每一行代表什么意思。
select b.NAME,a.VALUE from v$mystat a,v$statname b where a.STATISTIC#=b.STATISTIC# and b.name='redo size';
七、redo生成和before/after触发器。
1、before或者after触发器不影响delete生成的redo。
2、oracle9i release 2以及以前版本中,before或者after触发器会使insert生成同样数量的额外redo。在oracle 10g中,则不会生成任何额外的redo。
3、在oracle9i release 2 及以前的所有版本中,update生成的redo只受before触发器影响,after触发器不会增加任何额外的redo,不过oracle 10g中,
如果一个表没有触发器,对其更新期间生成的redo量总是比oracle9i及以前版本中要少。看来这是oracle着力解决的一个关键问题:对于无触发器的表,要减少这种表更新所生成的redo量。
在oracle 10g中,如果表有一个before触发器,则其更新期间生成的redo量比9i中更大。
如果表中after触发器,则更新所生成的redo量与9i中一样。
每个开发人员应该具备的能力:
1、估计你的"事务"大小(需要修改数据量)。
2、在修改的数据量基础上再增加10%-20%的开销,具体增加多大的开销取决于你的要修改的行数,修改得行数越多,增加的开销就越小。
3、对于update,要把这个估计值加倍。八、我能关掉redo日志生成吗?
答案:不能。
1、在sql中设置nologging:有些sql和操作支持nologging字句,这个对象的所有操作在执行时都不生成重做日志,而是说有些特定操作生成的redo会比平常少的多。
select * from v$database;
--改成archivelog mode
shutdown immediate
startup mount
alter database archivelog;
alter database open;
--改成noarchivelog mode
shutdown immediate
startup mount
alter database noarchivelog;
alter database open;
----------------------------------
drop table t;
@ 'C:\Oracle\mystat' "redo size"
create table t as select * from all_objects;
@ 'C:\Oracle\mystat' "redo size"
drop table t;
@ 'C:\Oracle\mystat' "redo size"
create table t nologging as select * from all_objects;
@ 'C:\Oracle\mystat' "redo size"
在noarchiving mode的数据库中,除了数据字典的修改外,create table不会记录日志,create index/drop index生成日志。
关于nologging操作,需要注意地方:
虽然是nologging mode,还会生成少量的redo,这些redo作用是保护数据字典。
nologging不能避免所有后续操作生成redo,在前面例子中,dml操作还会正常生成redo日志,sql*loader、insert /*append*/语法不生成日志。
在一个archivelog模式的数据执行nologging操作后,必须尽快为受影响的数据文件建立一个新的基准备份,从而避免由于介质失败而丢失对这些对象的后续修改。
2、nologing小结:
索引的创建和alter(rebuild)。
表的批量insert(通过/*append*/)或者采用sql*loader,表数据不生成redo。
lob操作。
通过create table as select 创建表。
各种alter table 操作。
在一个archiveing mode数据库中适当使用nologging,可以加快许多操作的速度。
九、为什么不能分配一个新日志?
dbwr、lgwr、arch进程操作时异步,如果dbwr还没有完成redo日志所保护数据的检查点,或者arch还没有把rdo日志文件复制到归档目标,就发生checkpoint not complete或者archival required。
解决办法:
1、让dbwr更快一些,可以使用async i/o、使用dbwr i/o从属进程,或者使用多个dbwr进程。这个方法好处是:宁可ibuyong付出什么代价就能有所收获,性能提高,而且不必修改如何逻辑/结构/代码。
2、增加更多重做日志文件。这种方法:可以消除系统中的"暂停",其缺点是会消耗更多的磁盘空间。
3、重新创建更大的日志文件。
4、让检查点发生得更频繁,此方法很不可取。
十、块清除。
block cleanout:即删除所有修改数据块上与"锁定"有关的信息。
有二个场合会做block cleanout,在commit时候,在SGA中的数据块中"锁定"信息会被清除掉,不在SGA中的数据块的将被忽略;这些被忽略的块会在第一次访问时候被清除。
十一、日志竞争
出现日志竞争时,数据库会提示“cannot allocate new log”。
原因可能是:redo放在一个慢速设备上。
redo与其他频繁访问的文件放在一个设备上。
以缓冲方式装载日志设备。
redo采用一个慢速技术,比如RAID-5.
解决方法:每组的redo日志文件放在不同的磁盘上;使用快速的设备;以raw磁盘装载日志。
十二、临时表和redo/undo
临时表不会为它们的块生成redo。因此,对临时表的操作不是“可恢复的”,修改临时表中的一个块时,不会将这个这个修改记录到重做日志文件中,不过,临时表确实会生成undo,而这个undo会记入日志,因此,临时表也会生成一些redo。
临时表的作用一般是insert和select为主。
十三、分析undo
1、dml操作生成undo情况
一般来说,insert生成的undo最少,因为oracle为此所需要记录的只是要“delete”的一个rowid;update一般排名第二,对于update,只需记录修改的字节;delete一般生成的redo最多,对于delete,oracle必须把整行的前映像记录到undo段中。
如何测量?
在事务中,可以通过v$transaction.used_ublk字段察看。
drop table t;
create table t as select object_name unindexed,object_name indexed from all_objects ;
create index t_inx on t(indexed);
exec dbms_stats.gather_table_stats(user,'T');
SELECT used_ublk from v$transaction where addr =(select taddr from v$session where sid=(select sid from v$mystat where rownum=1));
update t set unindexed=lower(unindexed) ;
update t set indexed=lower(indexed);
2、ORA-01555:snapshot too old 错误
错误原因:undo段太小,不足以在系统上执行工作;你的程序跨commit获取;块清除。
解决方案:适当地设置参数undo_retention(要大于执行查询嘴上的事务所需的时间),可以用v$undostat来确定长时间运行的查询的持续时间,另外,要确保磁盘上已经预留了足够的空间,使undo段能根据所请求的undo_retention增大。
使用手动的undo管理时加大或者增加更多的回滚段。这样在长时间运行的查询执行期间,覆盖undo数据的可能性降低。
减少查询的运行时间(调优)。
收集相关对象的停机信息,这个有助避免块清除导致的错误。
3、undo段大小确定
undo段管理方法:
自动undo管理:通过undo_retention参数告诉oracle要把undo段保留多少时间。oralce根据并发工作负载来确定要创建多少个undo段,以及每个undo段应该多大,这个undo管理的推荐方法。
手动undo管理:dba根据估计和观察到工作负载,确定手动的创建多少个undo段,dba根据事务量和长时间运行查询的长度来确定这些undo段应该多大。(问题:控制的参数有多少?)
在手动undo管理中,undo段不会因为查询而扩大,只有insert、update和delete才会让undo段增长。所以dba需要定时调整undo段的大小。
在手动undo管理中,回收机制是首先回收最小的undo段,如果所有的undo段的大小相同,会回收最老的undo段。因此遇到ora-01555错误可能是系统中最小的回滚段指示的,就算你增加一个大的undo段也不能解决此问题,故建议设置一致的undo段大小。
4、延迟块清除
在块清除过程中,如果一个块已经修改,下一个会话访问这个块时,可能必须查看最后一个修改这个块的事务是否还是活动。一旦确定该事务不再活动,就会完成块清除,这样另外一个会话访问这个块时就不必再经历同样的过程。要完成块清除,oracle会从块首部确定前一个事务所用的undo段,然后确定从undo首部能不能看出这个事务很久以前就已经提交,它在undo段事务表中的事务槽以及被覆盖,另一种情况是commit scn孩子undo段的事务表中,这说明该事务只是刚刚提交。