0

ORA-01555成因与解决

Eygle 2019-07-24
54
摘要:ORA-01555成因与解决

前面提到了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的回滚机制,块清除机制有所了解。


「喜欢文章,快来给作者赞赏墨值吧」

评论

0
0
Oracle
订阅
欢迎订阅Oracle频道,订阅之后可以获取最新资讯和更新通知。
墨值排行
今日本周综合
近期活动
全部
相关课程
全部