前面提到了ORA-01555错误,那么现在让我们看一下ORA-01555错误是怎样产生的。
我们知道,回滚段是循环使用的,当事务提交以后,该事务占用的回滚段事务表会被标记为非活动,回滚段空间可以被覆盖重用。
那么一个问题就出现了,如果一个查询需要使用被覆盖的回滚段构造前镜像实现一致性读,那么此时就会出现Oracle著名的ORA-01555错误。
ORA-01555错误的另外一个原因是因为延迟块清除(Delayed Block Cleanout)。当一个查询触发延迟块清除时,Oracle需要去查询回滚段获得该事务的提交SCN,如果事务的前镜像信息已经被覆盖,并且查询SCN也小于回滚段中记录的最小提交SCN,那么Oracle将无从判断查询SCN和事务提交SCN的大小,此时出现延迟块清除导致的ORA-01555错误。
另外一种导致ORA-01555错误的情况出现在使用sqlldr直接方式加载(direct=true)数据时。当通过sqlldr direct=true方式加载数据时,由于不产生重做和回滚信息,Oracle直接指定Cached Commit SCN给加载数据,在访问这些数据时,有时会产生ORA-01555错误。
看一下下图的描述:假定在时间T用户A发出一条更新语句,更新SCOTT用户的SAL;用户B在Ty时间发出查询语句,查询SCOTT用户的SAL;用户A的更新在Tx时间提交,提交可能为快速提交清除,也可能是延迟块清除;用户B的查询在Tz时间输出。
我们来看一下数据库在不同情况下的内部处理。
1. 如果Ty < T< Tz <Tx,那么查询需要构造一致性读,由于事务尚未提交,可以通过回滚段构造前镜像,完成一致性读取。
2. 如果Ty < T < Tx < Tz,由于Ty查询时间小于T事务更新时间,那么数据库需要构造一致性读取,而Tz查询完成时间大于Tx提交时间,那么前镜像就有可能被覆盖,不可获取。
如果Tx的提交方式为Fast Block Cleanout,那么回滚段信息不可用时就会出现一致性读ORA-01555错误。
如果Tx的提交方式为Delayed Block Cleanout,那么回滚段信息不可用时Oracle将无法判断Ty和Tx的时间先后关系。如果Ty>Tx,那么Oracle可以正常进行块清除,并将块清除后的数据返回给用户B;如果Ty<T,那么Oracle需要继续构造一致性读返回给用户B;Oracle无法判断这两种情况,就会出现延迟块清除ORA-01555错误。
ORA-01555的直观解释是“Snapshot too old”,也就是快照太旧,其根本含义就是查询需要的前镜像过于“久远”,已经无法找到了。
我们可以想象,如果一个历时数个小时或10数小时的查询,如果最后遭遇ORA-01555错误而失败,会是多么令人沮丧的一件事。一直以来,ORA-01555都是ORACLE最为头痛的问题之一。
当数据出现ORA-01555错误时,Oracle会将错误信息记录在警告日志中,从生产数据库中摘录ORA-01555错误信息举例如下。
这是一段ORA-01555错误信息,其中Query Duration=14616 sec 指查询经历的时间,这个时间已经超过了undo_retention的缺省值 10800的设置:
Mon Aug 1 15:03:39 2005 ORA-01555 caused by SQL statement below (Query Duration=14616 sec, SCN: 0x0000.1e5294a9): Mon Aug 1 15:03:39 2005 select sp.vc2planguid from cy_spinfo pvd, cy_serviceinfo svr, cy_serviceplan sp, cy_feetype ft where pvd.vc2spguid = svr.vc2spguid and svr.vc2serviceguid = sp.vc2serviceguid and sp.vc2ftguid = ft.vc2ftguid and to_char(sysdate, 'yyyyMMddhh24miss') between nvl(pvd.vc2startdate, to_char(sysdate, 'yyyyMMddhh24miss')) and nvl(pvd.vc2enddate, to_char(sysdate, 'yyyyMMddhh24miss')) and to_char(sysdate, 'yyyyMMddhh24miss') between nvl(sp.vc2startdate, to_char(sysdate, 'yyyyMMddhh24miss')) and nvl(svr.vc2enddate, to_char(sysdate, 'yyyyMMddhh24miss')) and
这个错误是由于一个job任务的执行导致的:
Mon Aug 1 15:03:39 2005 Errors in file /opt/oracle/admin/hsboss/bdump/hsboss_j000_1088.trc: ORA-12012: error on auto execute of job 181 ORA-01555: snapshot too old: rollback segment number 8 with name "_SYSSMU8$" too small ORA-06512: at "CYUSER.CYPKG_BILLING", line 385 ORA-06512: at line 1
在Oracle9i的文档中这样描述ORA-01555错误:
ORA-01555 snapshot too old: rollback segment number string with name "string" too small Cause: Rollback records needed by a reader for consistent read are overwritten by other writers. Action: If in Automatic Undo Management mode, increase the setting of UNDO_RETENTION. Otherwise, use larger rollback segments.
我们看到,在Oracle9i自动管理的UNDO表空间模式下,UNDO_RETENTION参数的引入正是为了减少ORA-01555错误的出现。
这个参数设置当事务提交之后(回滚段变得非激活),回滚段中的前镜像数据在被覆盖前保留的时间,该参数以秒为单位,9iR1初始值为900秒,在Oracle9iR2增加为10800秒。
显然该参数设置的越高就越能够减少ORA-01555错误的出现,但是保留时间和存储空间是紧密相关的,如果UNDO表空间的存储空间有限,那么ORACLE就会选择回收已提交事务占用的空间,置UNDO_RETENTION参数的设置于不顾。
在Oracle9i的AUM模式下,UNDO_RETENTION实际上是一个非但保(NO Guaranteed)限制。也就是说,如果有其他事务需要回滚空间,而空间出现不足时,这些信息仍然会被覆盖;从Oracle10g开始,Oracle对于UNDO增加了Guarantee控制,也就是说,你可以指定UNDO表空间必须满足UNDO_RETENTION的限制。当UNDO表空间设置为Guarantee,那么提交事务的回滚空间必须被保留足够的时间,如果UNDO表空间的空间不足,那么新事务会因空间不足而失败,而不是选择之前的覆盖。
从各个不同版本回滚段的管理变迁,我们可以看出,Oracle一直在进步。
Oracle提供一个内部事件(10203事件)可以用来跟踪数据库的块清除操作, 10203事件可以通过以下命令设置,设置后需要重新启动数据库该参数方能生效:
alter system set event="10203 trace name context forever" scope=spfile;
设置了10203事件之后,可以通过实验来研究一下ORA-01555错误的成因。为了实验的方便,首先创建一个手工管理不可扩展的小Undo表空间:
SQL> create undo tablespace undotbs 2 datafile '/opt/oracle/oradata/conner/undotbs.dbf' size 2m autoextend off; Tablespace created. SQL> alter system set undo_tablespace=undotbs; System altered. SQL> alter system set undo_management=manual scope=spfile; System altered. SQL> alter system set event="10203 trace name context forever" scope=spfile; System altered. SQL> shutdown immediate; SQL> startup SQL> select * from v$rollname; USN NAME ---------- ------------------------------ 0 SYSTEM SQL> create rollback segment rbs01 tablespace undotbs; Rollback segment created. SQL> alter rollback segment rbs01 online; Rollback segment altered. SQL> select * from v$rollname; USN NAME ---------- ------------------------------ 0 SYSTEM 21 RBS01
再来执行一个任务,首先打开一个游标,执行一个查询(在适当步骤加入了数据块转储及回滚段转储命令,并将跟踪信息同步进行讲解):
SQL> var cemp refcursor SQL> begin 2 open :cemp for select * from emp where empno=7788; 3 end; 4 / PL/SQL procedure successfully completed. SQL> alter system dump datafile 1 block 23642; System altered.
此时无DML事务进行,数据块ITL状态如下所示:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0015.045.000003b7 0x010000b1.2b39.04 C--- 0 scn 0x0819.0045cd56 0x02 0x0015.03f.000003b6 0x010000b6.2b2b.06 C--- 0 scn 0x0819.0045cb5c
然后更新一条记录并且提交,同时转储数据块的内容:
SQL> update emp set sal=4000 where empno=7788; 1 row updated. SQL> alter system dump datafile 1 block 23642; System altered.
此时数据块上ITL及具体记录上都记录了锁定信息,本例里ITL2(0x02)被使用:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0015.045.000003b7 0x010000b1.2b39.04 C--- 0 scn 0x0819.0045cd56 0x02 0x0015.049.000003b8 0x010000ac.2b46.02 ---- 1 fsc 0x0000.00000000
数据行LB指向了ITL 2:
tab 0, row 7, @0x1e7f tl: 40 fb: --H-FL-- lb: 0x2 cc: 8 col 0: [ 3] c2 4e 59 col 1: [ 5] 53 43 4f 54 54 col 2: [ 7] 41 4e 41 4c 59 53 54 col 3: [ 3] c2 4c 43 col 4: [ 7] 77 bb 04 13 01 01 01 col 5: [ 2] c2 29 col 6: *NULL* col 7: [ 2] c1 15
继续SQL*Plus中的操作,转储一下回滚段头信息:
SQL> alter system dump undo header RBS01; System altered.
回滚段第0x49号事务槽被使用:
index state cflags wrap# uel scn dba parent-xid nub stmt_num 0x49 10 0x80 0x03b8 0x0000 0x0819.0045cea3 0x010000ac 0x0000.000.00000000 0x00000001 0x00000000
继续,提交该事务,并再次转储回滚段头信息:
SQL> commit; Commit complete. SQL> alter system dump undo header RBS01; System altered.
可以看到该事务已经被提交,回滚段事务状态变为非激活(state=9),提交的SCN为:0x0819.0045cea4 。
index state cflags wrap# uel scn dba parent-xid nub stmt_num 0x49 9 0x80 0x03b8 0xffff 0x0819.0045cea4 0x010000ac 0x0000.000.00000000 0x00000001 0x00000000
继续,我们来看此时的数据块:
SQL> alter system dump datafile 1 block 23642; System altered.
ITL已经记录了事务提交,SCN/FSC表示Commit SCN或快速提交(Fast Commit Fsc)的SCN,这个SCN和UNDO中的提交SCN一致,是准确的SCN:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0015.045.000003b7 0x010000b1.2b39.04 C--- 0 scn 0x0819.0045cd56 0x02 0x0015.049.000003b8 0x010000ac.2b46.02 --U- 1 fsc 0x0000.0045cea4
此时的数据行锁定位并不需要清除:
tab 0, row 7, @0x1e7f tl: 40 fb: --H-FL-- lb: 0x2 cc: 8 col 0: [ 3] c2 4e 59 col 1: [ 5] 53 43 4f 54 54 col 2: [ 7] 41 4e 41 4c 59 53 54 col 3: [ 3] c2 4c 43 col 4: [ 7] 77 bb 04 13 01 01 01 col 5: [ 2] c2 29 col 6: *NULL* col 7: [ 2] c1 15
此时10203事件的跟踪信息被记录,块清除记录了确切的SCN信息:
Begin cleaning out block ... Found active transactions Block cleanout record, scn: 0x0819.0045cea6 ver: 0x01 opt: 0x02, entries follow... itli: 2 flg: 2 scn: 0x0819.0045cea4 Block cleanout under the cache... Block cleanout record, scn: 0x0819.0045cea6 ver: 0x01 opt: 0x02, entries follow... itli: 2 flg: 2 scn: 0x0819.0045cea4 ... clean out dump complete. Start dump data blocks tsn: 0 file#: 1 minblk 23642 maxblk 23642
继续我们在SQL*Plus中的操作,执行一个批处理更新,由于我们只有一个用户回滚段,先前的事务信息很快被覆盖:
SQL> begin 2 for i in 1 .. 100 loop 3 update emp set sal=4000; 4 rollback; 5 end loop; 6 end; 7 / PL/SQL procedure successfully completed. SQL> alter system dump datafile 1 block 23642;
我们再看此时的数据块信息,数据块被写出,锁定位被清除,ITL Flag标志位置为提交状态(C-Commit):
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0015.045.000003b7 0x010000b1.2b39.04 C--- 0 scn 0x0819.0045cd56 0x02 0x0015.049.000003b8 0x010000ac.2b46.02 C--- 0 scn 0x0819.0045cea4
数据行锁定位锁定信息清除:
tab 0, row 7, @0x1e81 tl: 40 fb: --H-FL-- lb: 0x0 cc: 8 col 0: [ 3] c2 4e 59 col 1: [ 5] 53 43 4f 54 54 col 2: [ 7] 41 4e 41 4c 59 53 54 col 3: [ 3] c2 4c 43 col 4: [ 7] 77 bb 04 13 01 01 01 col 5: [ 2] c2 29 col 6: *NULL* col 7: [ 2] c1 15
最后输出游标查询结果,由于游标打开时间在更新操作之前,Oracle需要构造一致性读,获取前镜像信息,而前镜像信息已经被覆盖,所以出现ORA-01555错误,这是最常见的1555错误来源,时间过长的查询很容易因ORA-01555错误而失败:
SQL> print :cemp ERROR: ORA-01555: snapshot too old: rollback segment number 21 with name "RBS01" too small no rows selected
再来看看第二种情况,同样构造一个游标打开进行查询:
SQL> var cemp refcursor SQL> begin 2 open :cemp for select * from emp where empno=7788; 3 end; 4 / PL/SQL procedure successfully completed.
然后更新数据:
SQL> update emp set sal=4000 where empno=7788; 1 row updated. SQL> alter system dump undo header RBS01; System altered.
此时的UNDO事务表事务槽信息如下:
index state cflags wrap# uel scn dba parent-xid nub stmt_num 0x43 10 0x80 0x03bd 0x0001 0x0819.0045f09c 0x010000b7 0x0000.000.00000000 0x00000001 0x00000000
在提交之前,我们强制刷新Buffer Cache,写出脏数据:
SQL> alter session set events = 'immediate trace name flush_cache'; Session altered.
然后提交,此时,Oracle将执行延迟块清除:
SQL> commit; Commit complete. SQL> alter system dump datafile 1 block 23642; System altered. SQL> alter system dump undo header RBS01; System altered.
由于事务已经提交,回滚段事务表已经被标记为非活动,此时的提交SCN为:0x0819.0045f09e :
index state cflags wrap# uel scn dba parent-xid nub stmt_num 0x43 9 0x80 0x03bd 0xffff 0x0819.0045f09e 0x010000b7 0x0000.000.00000000 0x00000001 0x00000000
此时的数据块已经被写出到数据文件,Oracle执行延迟块清除,ITL事务槽及锁定信息仍然在Block上存在:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0015.043.000003bd 0x010000b8.2b7b.04 ---- 1 fsc 0x0000.00000000 0x02 0x0015.041.000003bd 0x010000b8.2b7b.02 C--- 0 scn 0x0819.0045f093
执行批处理更新,覆盖前镜像信息:
SQL> begin 2 for i in 1 .. 100 loop 3 update emp set sal=4000; 4 rollback; 5 end loop; 6 end; 7 / PL/SQL procedure successfully completed.
此时的ORA-01555错误就是因为延迟块清除所导致的:
SQL> print :cemp ERROR: ORA-01555: snapshot too old: rollback segment number 21 with name "RBS01" too small no rows selected
此时的延迟块清除被跟踪记录:
Begin cleaning out block ... Found all committed transactions Block cleanout record, scn: 0xffff.ffffffff ver: 0x01 opt: 0x01, entries follow... itli: 1 flg: 2 scn: 0x0819.0045f09e Block cleanout under the cache... Block cleanout record, scn: 0x0819.0045f09f ver: 0x01 opt: 0x01, entries follow... itli: 1 flg: 2 scn: 0x0819.0045f09e ... clean out dump complete.
如果此时DUMP日志文件,也可以看到块清除信息(我们看到此时,由于延迟块清除也产生了日志,也就是在查询时可能看到的日志生成):
REDO RECORD - Thread:1 RBA: 0x000068.00000006.0010 LEN: 0x003c VLD: 0x01 SCN: 0x0819.0045f09f SUBSCN: 1 04/20/2006 21:50:33 CHANGE #1 TYP:0 CLS: 1 AFN:1 DBA:0x00405c5a SCN:0x0819.0045f09c SEQ: 1 OP:4.1 Block cleanout record, scn: 0x0819.0045f09f ver: 0x01 opt: 0x01, entries follow... itli: 1 flg: 2 scn: 0x0819.0045f09e
这里的OP(Operation Code):4.1 就是指Block Cleanout 。来看一下此时的数据块信息:
SQL> alter system dump datafile 1 block 23642; System altered.
数据块的ITL和锁定信息都被清除:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0015.043.000003bd 0x010000b8.2b7b.04 C--- 0 scn 0x0819.0045f09e 0x02 0x0015.041.000003bd 0x010000b8.2b7b.02 C--- 0 scn 0x0819.0045f093
需要注意的是,可能存在另外一种情况,就是当执行延迟块清除时,回滚段或原回滚表空间已经被删除,此时Oracle仍然可以通过字典表UNDO$来获得SCN信息,执行块清除。
关于Oracle的提交处理及块清除机制是一个极其复杂的过程,本文对这部分内容进行了适当简化说明,旨在让大家能够对Oracle的回滚机制,块清除机制有所了解。