ORA-01555(快照过旧)问题让很多人感到十分头痛。最近我们的生产系统上也报出了ORA-01555错误。就结合这次案例将ORA-1555问题作个案例分析,并浅析产生原因和各种解决办法。
如果要了解1555错误产生的原因,就需要知道ORACLE的两个特性:
一致性读和延迟块清除。
(1)一致性读:
当Oracle更新数据块(Data Block Oracle中最小的存储单位)时,会在两个地方记录下这一更新动作。
一个是重做段(Redo Segment),是用于数据库恢复(Recover)用的。
一个是回滚段(UNDO Segment),而回滚段是用于事务回滚(Rollback)的(我们只关心回滚段了)。
并在数据块头部标示出来是否有修改数据。一个语句在读取数据快时,如果发现这个数据块是在它读取的过程中被修改的(即开始执行读操作时并没有被修改),就不直接从数据块上读取数据,而是从相应的回滚段条目中读取数据。这就保证了最终结果应该是读操作开始时的那一时刻的快照(snapshot),而不会受到读期间其他事务的影响。这就是Oracle的一致性读,也可以叫做多版本(Multi-Versioning)。
(2)延迟块清除:我们知道,当Oracle更新数据块时,会在回滚段(UNDO Segment)记录下这一更新动作。并且产生一个Cleanout SCN,在回滚段中,会产生对应的Transaction ID以及相应的数据记录镜像。并在对应的数据记录上,产生锁标志。在事务提交(commit)前,会在数据块的头部记录下这个Cleanout SCN(Csc)号、Undo Block Address(Uba)和Transaction ID(Xid);并且在在对应InterestedTransaction List(Itl)中设置锁标志,记录这个事务在这数据块中产生的锁的数目;同时在对应修改的数据记录上打上行级锁标志,并映射到对应的Itl去。当提交时,并不会一一清除掉所有锁标志,而是给对应的Itl打上相应标志,告诉后面访问该数据块的事务,相应的事务已经提交。这就叫做快速提交(Fast Commit)。而后面访问该数据块的的事务就先检查锁标志和对应的事务状态,如果发现前面的事务没有提交,并且要访问的数据记录被锁住了,就被阻塞;否则就清除相应的锁标志,并提交自己的锁标志,再重复以上动作。这就是延迟块清除。
而如果前面的事务在提交之前buffercache中的脏数据已经被DBwn进程写回,那么Itl中的事务标志就不会被更新,并且数据块的Itl列表也不会记录下事务的Commit SCN。后面的事务或查询语句访问该数据块时,为了检测是否需要进行一致性读(如果数据块的Itl中记录的提交事务的Commit SCN大于当前访问该数据块的SCN,则需要进行一致性读),就需要通过Undo Block Address和Transaction ID到回滚段的事务信息表中去检查前面事务的状态和它的Commit SCN,确定是否做一致性读,最后将前面事务在该数据块上的标志做一次Cleanout。
举例如下:创建测试表:
SQL> create table t_multiver (a number,b number);
Table created.
插入测试数据,这时,实际上已经产生了一个对数据块修改的事务:
SQL> insert into t_multiver values(1,1);
1 row created.
SQL> insert into t_multiver values(2,2);
1 row created.
SQL> insert into t_multiver values(3,3);
1 row created.
SQL>
SQL> commit;
Commit complete
修改记录,并且在commit之前将脏数据写回:
SQL> conn demo/demo
Connected.
SQL> update t_multiver set b=115 wherea=1;
1 row updated.
SQL> alter system flush buffer_cache;
System altered.
SQL> commit;
Commit complete.
Dump出数据块:
SQL> alter system dump datafile 5 block50959;
System altered.
看看Dump出来的内容:
Block header dump: 0x00417149
Object id on Block? Y
seg/obj: 0x157db csc: 0x00.2c96f3 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0007.020.00000463 0x00c00bf5.012f.28 C--- 0 scn 0x0000.002c96be
0x02 0x0008.005.0000053b 0x00c0025b.020a.27 ---- 1 fsc 0x0000.00000000
bdba: 0x00417149
data_block_dump,data header at0x7fa1b9f07a5c
===============
tsiz: 0x1fa0
hsiz: 0x18
pbl: 0x7fa1b9f07a5c
76543210
flag=--------
ntab=1
nrow=3
frre=-1
fsbo=0x18
fseo=0x1f7b
avsp=0x1f6c
tosp=0x1f6c
0xe:pti[0] nrow=3 offs=0
0x12:pri[0] offs=0x1f7b
0x14:pri[1] offs=0x1f8e
0x16:pri[2] offs=0x1f85
block_row_dump:
tab 0, row 0, @0x1f7b
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f8e
tl: 9 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 03
col 1: [ 2] c1 03
tab 0, row 2, @0x1f85
tl: 9 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 04
col 1: [ 2] c1 04
end_of_block_dump
GLOBAL CACHE ELEMENT DUMP (address:0x69ff22f8):
id1: 0x17149 id2: 0x1 pkey: OBJ#88027 block: (1/94537)
lock: C rls: 0x0 acq: 0x0 latch: 3
flags: 0x21 fair: 0 recovery: 0 fpin: 'ktswh23: ktsfbkl'
bscn: 0x0.2c96f3 bctx: (nil) write: 0 scan: 0x0
lcp: (nil) lnk: [NULL] lch: [0x693e1a68,0x683fae28]
seq: 21 hist: 197 48 121 113 238 180 58 143:0 208 352 197 48 121 113 238180
58
GCSSHADOW 0x69ff2370,3 resp[(nil),0x17149.1] pkey 88027.0
grant 0 cvt 0 mdrole 0x0 st 0x0 lst 0x40 GRANTQ rl LOCAL
master 1 owner 1 sid 0 remote[(nil),0] hist 0x206181030c
history 0xc.0x6.0x4.0xc.0x6.0x4.0x0.0x0.0x0.0x0.
cflag 0x0 sender 0 flags 0x0 replay# 0 abast (nil).x0.1 dbmap (nil)
disk: 0x0000.00000000 write request: 0x0000.00000000
piscn: 0x0000.00000000 sq[0x69ff2370,0x69ff2370]
msgseq 0x0 updseq 0x0 reqids[2,0,0] infop (nil) lockseq x19f
pkey 88027.0
hv114 [stat 0x0, 1->1, wm 32768, RMno 0, reminc 0, dom 0]
kjga st 0x4, step 0.0.0, cinc 2, rmno 1, flags 0x0
lb0, hb 0, myb 15913, drmb 15913, apifrz 0
GCSSHADOW END
2016-08-14 04:17:56.031464 : kjbmbassert[0x17149.1]
End dump data blocks tsn: 0 file#: 1 minblk94537 maxblk 94537
其余的内容在我们讨论的这个问题中不需要太关心,主要注意Interested Transaction Slot (ITS) 部分。
CSC:即Cleanout SCN,它是在我们的insert操作事务中产生的。
Flag:事务标志位。由于我们在提交之前将buffercache手动flush了,所以标志位为空。请注意到,我们这在commit之前DBwn已经写回了脏数据,标志为空。
各个标志的含义分别是:
C--- = transaction has been committed and lockscleaned out
-B-- = this undo record contains the undo for thisITL entry
--U- = transaction committed (maybe long ago); SCN isan upper bound
---T = transaction was still active at block cleanoutSCN
可以看到,目前事务标志是----,这是为什么呢?请注意,上面过程在commit之前进行了buffer cache flush,也就是说,oracle进程在改写数据块时,该事务还未提交,也未回滚,所以标志为空。而假如将buffer cache flush放在commit之后,该标致就为--U-,即事务已经提交,但是相应的锁并没有清除(有兴趣可以自己做试验)。所以,看到后面的Lck位(行级锁数目)为1(因为我们修改了1条记录)。
再看每条记录中的行级锁对应Itl条目lb:都是0x2。即Itl中的第2条。
这时,我们重新访问该数据块:
SQL> alter system flush buffer_cache;
System altered.
SQL> conn demo/demo
Connected.
SQL> select * from t_multiver;
A B
---------- ----------
1 115
2 222
3 222
SQL> alter system dump datafile 5 block50959;
System altered.
再将数据块内容dump出来:
Block header dump: 0x00417149
Object id on Block? Y
seg/obj: 0x157db csc: 0x00.2c9847 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0007.020.00000463 0x00c00bf5.012f.28 C--- 0 scn 0x0000.002c96be
0x02 0x0008.005.0000053b 0x00c0025b.020a.27 C--- 0 scn0x0000.002c96f8
bdba: 0x00417149
data_block_dump,data header at0x7fec9b50aa5c
===============
tsiz: 0x1fa0
hsiz: 0x18
pbl: 0x7fec9b50aa5c
76543210
flag=--------
ntab=1
nrow=3
frre=-1
fsbo=0x18
fseo=0x1f7b
avsp=0x1f6c
tosp=0x1f6c
0xe:pti[0] nrow=3 offs=0
0x12:pri[0] offs=0x1f7b
0x14:pri[1] offs=0x1f8e
0x16:pri[2] offs=0x1f85
block_row_dump:
tab 0, row 0, @0x1f7b
tl: 10 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f8e
tl: 9 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 03
col 1: [ 2] c1 03
tab 0, row 2, @0x1f85
tl: 9 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 04
col 1: [ 2] c1 04
end_of_block_dump
这时,可以看到,前一事务的Itl条目中,Flag标志为已经被修改为C,即提交完毕,Commit SCN也被获得。锁也已经被清除,其锁Lck的数量也清0。相应的,各条记录的行锁对应Itl位也被清0。
drop table t_multiver;
create table t_multiver (a number, bnumber);
insert into t_multiver values (1,1);
insert into t_multiver values (2,2);
insert into t_multiver values (3,3);
commit;
update t_multiver set b=115 where a=1;
select rowid from t_multiver;
commit;
alter system flush buffer_cache;
SQL> selectdbms_rowid.ROWID_RELATIVE_FNO(rowid),dbms_rowid.ROWID_BLOCK_NUMBER(rowid) fromt_multiver;
DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID)DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------------------------------------------
1 94537
1 94537
1 94537
Block header dump: 0x00417149
Object id on Block? Y
seg/obj: 0x157dc csc: 0x00.2c992d itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0007.007.00000463 0x00c00bf5.012f.33 C--- 0 scn 0x0000.002c9920
0x02 0x0006.002.000005d2 0x00c003aa.01b2.1d --U- 1 fsc 0x0000.002c9934
bdba: 0x00417149
data_block_dump,data header at 0x7f19a8466a5c
===============
tsiz: 0x1fa0
hsiz: 0x18
pbl: 0x7f19a8466a5c
76543210
flag=--------
ntab=1
nrow=3
frre=-1
fsbo=0x18
fseo=0x1f7b
avsp=0x1f6c
tosp=0x1f6c
0xe:pti[0] nrow=3 offs=0
0x12:pri[0] offs=0x1f7b
0x14:pri[1] offs=0x1f8e
0x16:pri[2] offs=0x1f85
block_row_dump:
tab 0, row 0, @0x1f7b
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 10
tab 0, row 1, @0x1f8e
tl: 9 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 03
col 1: [ 2] c1 03
tab 0, row 2, @0x1f85
tl: 9 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 04
col 1: [ 2] c1 04
end_of_block_dump