场景一: 会话一更新一小表,更新的block数小于db_block_buffers*10%,当会话一进行commit时,更新的block还存在于buffer cache中。 由于更新的块数较少,Oracle采用SO(state block)列表来管理更新块。SO主要包括了更新块的事物状态信息。 当会话一进行提交时,会从SO列表中获取更新块的事物信息,从而进行更新块的事物信息清理。清理主要包括块头itl状态列的更新(Flag,Lck ,Scn/Fsc)和 和行锁标记的清理。Oracle对于这种块清理称之为快速块清理。(Fast Block Cleanout)。 发生延迟块清除后,块头的Flag往往显示如下: Itl           Xid                  Uba         Flag  Lck        Scn/Fsc 0×01   0×0009.01f.0000bed8  0×04c00016.13bf.28  –U-    3  fsc 0×0000.063f1a51 其中U表示upper bound,意味着快速块清除。 场景二: 会话一更新一大表,更新的blocks数大于db_block_buffers*10%,当会话一进行commit前,部分更新的block由于种种原因已经刷新至至数据文件。 当会话进行commit时,Oracle对存在于SO列表的更新块进行快速块清理,同时更新undo segment head中的事物槽(将事物槽state标记从10置回9,表示该事务已经提交)。 但需要注意的是,Oracle在commit时,并不会处理已刷新在数据文件中的block(此时数据块头还flag标记仍然显示未提交状态,Lck依然显示该块中行锁的行数,Scn/Fsc为空,行锁标记依然为lb: 0×2)。 Oracle采用此方进行提交时,并不需要再次将该事务涉及到的block从物理文件中读取,而只需要进行快速块清理和更新undo segment head中的事物槽即可。从而保证提交的性能最优化。 当有另一会话再次读取已刷新至数据文件的block时,如果发现此block itl列表有未提交事物时,那么Oralce将进行延迟块清除(defered block cleanout)。 当进行延迟块清除时,Oracle将获取数据块中未提交事物的xid中的seq和undo segment head中事物槽的seq进行比较。 如例子所示,数据块的itl显示如下: Itl           Xid                  Uba         Flag  Lck        Scn/Fsc 0×01   0×0009.018.0000bdd4  0×04c00016.13a5.32  C-U-    0  scn 0×0a00.063f0838 0×02   0×0009.01f.0000bed8  0×04c00016.13bf.28  —-    3  fsc 0×0000.00000000 undo segment head的和此事物相关的事物槽显示如下: index  state cflags  wrap#    uel         scn            dba ————————————————————— 0×1f    9    0×00  0xbed8  0xffff  0×0a00.063f1a51  0×04c00016 undo segment head的事物控制列表如下: TRN CTL:: seq: 0×13c0 chd: 0×002d ctl: 0×001f inc: 0×00000000 nfb: 0×0000 mgc: 0×8201 xts: 0×0068 flg: 0×0001 opt: 2147483646 (0×7ffffffe) uba: 0×04c0000d.13c0.04 scn: 0×0a00.063f17f1 事物控制列表中scn主要表示事物槽被覆盖前的scn。 如上所示itl 0×02中有未提交事物,那么首先会获取0×02事物中xid(回滚段为0×0009,事物槽为0×01f,seq为 0000bed8)和undo segment head对应事物槽比较(主要比较xid.seq和事物槽的wrap#)。 如果两者相等,那么进行延迟块清除时,Oracle将获取事物槽中的scn写往itl列表中的Scn/Fsc列。 如果两者不相等,这里分为两种情况: 1、undo的事物槽被其他事物覆盖,那么进行延迟块清除时,Oracle将获取undo segment head的事物控制列表的scn写往itl列表中的Scn/Fsc列。 这里需要注意的是,此时的scn并不是事物提交时的真正scn,而且肯定比事物提交时的scn大,那这样会不会影响事物一致性呢,接下来例子会进行说明。 2、undo表空间被删除,此时undo$并不会将此表空间删除,只会将STATUS$从2改为1,而且每个undo segment依然保留着最近一次提交的scn。如下所示 SQL> select SCNBAS,SCNWRP,STATUS$,file#,name from undo$; SCNBAS     SCNWRP    STATUS$      FILE# NAME ———- ———- ———- ———- —————————— 258954408       2560          1         13 _SYSSMU127$ 258954399       2560          1         13 _SYSSMU128$ 258954395       2560          1         13 _SYSSMU129$ 258954404       2560          1         13 _SYSSMU130$ 那么进行延迟块清除时,Oracle会去undo$中对获取对应的undo segment中的scn写往itl列表中的Scn/Fsc列。 同样需要注意的是,此时的scn并不一定事物提交时的真正scn,可能会比提交的scn大。发生延迟块清除后,块头的Flag往往显示如下: Itl           Xid                  Uba         Flag  Lck        Scn/Fsc 0×01   0×0009.018.0000bdd4  0×04c00016.13a5.32  C-U-    0  scn 0×0a00.063f0838 其中C表示该事物已结束,U表示Upper bound,C-U-往往表示延迟块清除。 延迟块清除和ora-01555 ora-01555这个经典错误就不解释了,这里主要研究延迟块清除导致的ora-01555。 假设有如下场景: 会话一: update test set id=’aaa’; commit; 其游标打开scn为10。 会话二: var x refcursor exec open   for select id from test; 其游标打开scn为20。 会话三: select id from test; 其游标打开scn为100。 进行延迟块清理,需要注意的是此时undo segment事物槽已经被覆盖,如上分析,此时Oracle将获取undo segment head的事物控制列表的scn写往itl列表中的Scn/Fsc列。 此时会话二获取结果将会发生什么情况呢?最理想状态,应该是获取aaa。实验过程如下(注意时间戳): 会话一:进行表格test更新,注意其游标打开的scn比10995221078145略大,dump该block可以清楚知道该block变化时的精确scn。 15:39:35 SQL> SELECT to_char(dbms_flashback.get_system_change_number) FROM dual; TO_CHAR(DBMS_FLASHBACK.GET_SYSTEM_CHANGE —————————————- 10995221078145 15:39:40 SQL> update test set id=999; 3 rows updated. 会话二:buffer_cache刷出 15:40:14 SQL> alter session set events ‘immediate trace name flush_cache level 1′; Session altered. 会话一:进行提交 15:40:24 SQL> commit; Commit complete. 会话三:执行如下SQL,注意其打开游标的scn比10995221021163略大。 15:42:18 SQL> var x refcursor 15:42:19 SQL> SELECT to_char(dbms_flashback.get_system_change_number) FROM dual; TO_CHAR(DBMS_FLASHBACK.GET_SYSTEM_CHANGE —————————————- 10995221078200 15:42:19 SQL> exec open   for select id from test; PL/SQL procedure successfully completed. 会话二:观察数据块itl列表和undo segment head事物槽 15:45:02 SQL> alter system dump datafile 9 block 566; System altered. 可以看到在经过缓存刷出之后,尽管事务已经提交。该数据块flag依然标记为—-,表示为有事物未提交。其block的scn为0×0a00.063f2082(10995221078146)。 *** SESSION ID 40.262) 2011-06-14 15:45:04.836 Start dump data blocks tsn: 9 file#: 9 minblk 566 maxblk 566 buffer tsn: 9 rdba: 0×02400236 (9/566) scn: 0×0a00.063f2082 seq: 0×03 flg: 0×04 tail: 0×20820603 frmt: 0×02 chkval: 0×1f0f type: 0×06=trans data Block header dump:  0×02400236 Object id on Block? Y seg/obj: 0xae99  csc: 0xa00.63f2079  itc: 2  flg: E  typ: 1 – DATA brn: 0  bdba: 0×2400231 ver: 0×01 inc: 0  exflg: 0 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc 0×01   0×0009.023.0000bedc  0×04c00017.13c1.03  —-    3  fsc 0×0000.00000000 0×02   0×0009.01f.0000bed8  0×04c00016.13bf.28  C—    0  scn 0×0a00.063f1a51 data_block_dump,data header at 0xbb6f264 =============== tsiz: 0×1f98 hsiz: 0×18 pbl: 0×0bb6f264 bdba: 0×02400236 76543210 flag=——– ntab=1 nrow=3 frre=-1 fsbo=0×18 fseo=0×31 avsp=0×7f2 tosp=0×7f2 0xe:pti[0]      nrow=3  offs=0 0×12:pri[0]     offs=0×17be 0×14:pri[1]     offs=0×80b 0×16:pri[2]     offs=0×31 block_row_dump: tab 0, row 0, @0×17be tl: 2010 fb: –H-FL– lb: 0×1  cc: 2 col  0: [ 3]  c2 0a 64 col  1: [2000] 回滚段头dump后可以发现事物已处于提交状态。 15:45:53 SQL> alter system dump datafile 19 block 9; TRN CTL:: seq: 0×13c1 chd: 0×0000 ctl: 0×002e inc: 0×00000000 nfb: 0×0001 mgc: 0×8201 xts: 0×0068 flg: 0×0001 opt: 2147483646 (0×7ffffffe) uba: 0×04c00017.13c1.1d scn: 0×0a00.063f1f5f Version: 0×01 FREE BLOCK POOL:: uba: 0×04c00017.13c1.1f ext: 0×1  spc: 0xfb0 uba: 0×00000000.13be.24 ext: 0×0  spc: 0xd7a uba: 0×00000000.0000.00 ext: 0×0  spc: 0×0 uba: 0×00000000.0000.00 ext: 0×0  spc: 0×0 uba: 0×00000000.0000.00 ext: 0×0  spc: 0×0 TRN TBL:: index  state cflags  wrap#    uel         scn            dba            parent-xid    nub     stmt_num ———————————————————————————————— 0×23    9    0×00  0xbedc  0×0022  0×0a00.063f2098  0×04c00017  0×0000.000.00000000  0×00000001   0×00000000 会话二:再次发起大量事物,确保回滚段事务槽被覆盖 15:52:03 SQL> begin 15:52:14   2  for i in 1..6000 15:52:14   3  loop 15:52:14   4  insert into ttt values (1,’aa’); 15:52:14   5  commit; 15:52:14   6  end loop; 15:52:14   7  end; 15:52:14   8  / PL/SQL procedure successfully completed. 15:52:16 SQL> alter system dump datafile 19 block 9; System altered. 可以看到回滚槽被覆盖,其事务控制表的scn为0×0a00.063f4612(10995221087762)比会话一的scn 10995221078145大 TRN CTL:: seq: 0×13cd chd: 0×0012 ctl: 0×0011 inc: 0×00000000 nfb: 0×0001 mgc: 0×8201 xts: 0×0068 flg: 0×0001 opt: 2147483646 (0×7ffffffe) uba: 0×04c00014.13cd.12 scn: 0×0a00.063f4612 Version: 0×01 FREE BLOCK POOL:: uba: 0×04c00014.13cd.12 ext: 0×1  spc: 0×181a uba: 0×00000000.13be.24 ext: 0×0  spc: 0xd7a uba: 0×00000000.0000.00 ext: 0×0  spc: 0×0 uba: 0×00000000.0000.00 ext: 0×0  spc: 0×0 uba: 0×00000000.0000.00 ext: 0×0  spc: 0×0 TRN TBL:: index  state cflags  wrap#    uel         scn            dba            parent-xid    nub     stmt_num ———————————————————————————————— 0×20    9    0×00  0xbf59  0×0024  0×0a00.063f4625  0×04c00013  0×0000.000.00000000  0×00000001   0×00000000 。。。 0×24    9    0×00  0xbf59  0×0021  0×0a00.063f4627  0×04c00013  0×0000.000.00000000  0×00000001   0×00000000 此时发起延迟块清理,根据dump结果我们看到已经进行了延迟块清理 15:54:31 SQL> SELECT to_char(dbms_flashback.get_system_change_number) FROM dual; TO_CHAR(DBMS_FLASHBACK.GET_SYSTEM_CHANGE —————————————- 10995221087904 15:54:31 SQL> select id from test; ID ———- 999 999 999 15:56:13 SQL>  alter system dump datafile 9 block 566; System altered. 注意到0×0a00.063f46a0和延迟块清理SQL发起的scn一致,这说明此block的修改scn取自延迟块清理SQL发起的scn。 另外延迟块清理itl中scn 0×0a00.063f4627比事务控制列表中的scn 0×0a00.063f4612略大,这是由于事务控制列表中scn不停变化引起的(此处是因为0×24被覆盖导致事物控制列表scn变化。) *** SESSION ID 35.359) 2011-06-14 15:57:04.683 Start dump data blocks tsn: 9 file#: 9 minblk 566 maxblk 566 buffer tsn: 9 rdba: 0×02400236 (9/566) scn: 0×0a00.063f46a0 seq: 0×01 flg: 0×00 tail: 0×46a00601 frmt: 0×02 chkval: 0×0000 type: 0×06=trans data Block header dump:  0×02400236 Object id on Block? Y seg/obj: 0xae99  csc: 0xa00.63f46a0  itc: 2  flg: E  typ: 1 – DATA brn: 0  bdba: 0×2400231 ver: 0×01 inc: 0  exflg: 0 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc 0×01   0×0009.023.0000bedc  0×04c00017.13c1.03  C-U-    0  scn 0×0a00.063f4627 0×02   0×0009.01f.0000bed8  0×04c00016.13bf.28  C—    0  scn 0×0a00.063f1a51 data_block_dump,data header at 0xc7cf264 =============== tsiz: 0×1f98 hsiz: 0×18 pbl: 0×0c7cf264 bdba: 0×02400236 76543210 flag=——– ntab=1 nrow=3 frre=-1 fsbo=0×18 fseo=0×31 avsp=0×7f2 tosp=0×7f2 0xe:pti[0]      nrow=3  offs=0 0×12:pri[0]     offs=0×17be 0×14:pri[1]     offs=0×80b 0×16:pri[2]     offs=0×31 block_row_dump: tab 0, row 0, @0×17be tl: 2010 fb: –H-FL– lb: 0×0  cc: 2 col  0: [ 3]  c2 0a 64 col  1: [2000] 回到会话三:查看游标打开时数据,可以看到ora-01555如期出现。 16:06:31 SQL> print  ERROR: ORA-01555: snapshot too old: rollback segment number 9 with name “_SYSSMU9$” too small no rows selected 虽然一号会话的update游标scn比三号会话的游标scn小,但Oracle在读取block时,由于延迟块清除导致了此block itl槽的scn比三号会话的游标scn大, 此时三号会话并不能判断此block的事务什么时候结束,所以就报了ora-01555。