一、ITL与事务的关系
ITL(interested transaction list)事务槽是Oracle数据块内部的一个组成部分,位于数据块头(block header)。ITL由xid、uba、flag、lck和SCN/fsc组成,用来记录在该数据块上所有发生的事务。一个ITL槽位可以看作是一条事务记录,它是Oracle中事务处理的关键组件,如果事务已经提交,则该ITL槽位就可以被反复使用,如果一直不提交,则该ITL槽位一直被占用,里面记录着事务信息、回滚段入口、事务类型等。事务提交后,ITL槽位中仍保存着该事务提交时的SCN号。
ITL最小值为1,由参数initrans控制(由于兼容性的原因,Oracle会在对象的存储块上分配两个ITL,因此inittrans的最小值实际上为2),这也是在建表时如果不指定initrans参数时的默认取值,最大值为255,由参数maxtrans控制,最大值参数在Oracle 10g以后不能被修改。一个ITL占用块46B的空间,当块中还有一定的free space时,Oracle可以使用free space构建ITL供事务使用,如果没有了free space,则块因为不能分配新的ITL就可能发生ITL等待。
当用户发出一条SQL语句时,Oracle会记录下这个时刻的SCN,然后在buffer cache中查找需要的block,或者从磁盘上读取,当别的会话修改了数据,或者正在修改数据,就会在相应的block上记录ITL,此时Oracle发现ITL中记录的SCN大于select时刻的SCN,那么Oracle就会根据ITL中记录的uba找到undo信息,获得该block的前镜像,然后在buffer cache中构造CR(consistent read )块,此时Oracle也会检查构造出来的block中ITL记录的SCN,如果SCN仍然大于select时刻的SCN,那么将继续重复构造前镜像,直到前镜像block中ITL记录的SCN小于select时刻的SCN,同时检查该事务是否提交或回滚,如果没有,还要继续构造前镜像,直到找到需要的block。如果在构造前镜像过程中所需的undo信息被覆盖了,就会报快照过旧的错误。于是Oracle实现了多版本控制,这就是Oracle多版本的本质,这也就是为什么发出一条select语句时总是会看到consistent gets了。
二、ITL等待
发生ITL等待的场景有以下两种情况:
1、超过了maxtrans配置的最大ITL数;
2、initrans配置不足,且没有足够的free space开扩展ITL。
解决办法:
maxtrans不足:高并发引起,同一数据块上的事务量已经超出了允许的ITL数量。因此需要减少事务的并发量,对于长事务,在保证数据完整性的前提下,增加commit的频率,将长事务变为短事务,以减少资源占用。
initrans不足:数据块上的ITL数量并没有达到maxtrans的限制,发生这种情况的表通常是被较多的update,造成预留空间pctfree(默认10%)被填满。此时可增加表的initrans或pctfree来解决,如果该表上事务的并发量高,可优先增加initrans,增大ITL槽位的初始分配量,反之,则优先增加pctfree,提升ITL槽位的扩展能力。
注意:如果是通过alter table方式修改了表的这两个参数,那么只会影响新的数据块,而不会改变已有数据的数据块。
三、实验验证ITL与事务的关系
连接到scott用户
sqlplus scott/tiger
创建测试表,pctfree设为0
create table t1(a number, b varchar2(30)) pctfree 0;
begin
for i in 1 .. 1000 loop
insert into t1 values (i, 'data');
end loop;
commit;
end;
/
查看段的区间分配信息
col segment_name for a20
col tablespace_name for a20
select segment_name, segment_type, tablespace_name, extent_id, file_id, block_id, blocks, bytes from dba_extents where owner = 'SCOTT' and segment_name = 'T1';
SEGMENT_NAME SEGMENT_TYPE TABLESPACE_NAME EXTENT_ID FILE_ID BLOCK_ID BLOCKS BYTES
-------------------- ------------------ -------------------- ---------- ---------- ---------- ---------- ----------
T1 TABLE USERS 0 4 168 8 65536
查看块分配信息
select distinct dbms_rowid.rowid_block_number(rowid) from scott.t1;
DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------
171
174
由此可知,t1表的数据占用了两个数据块,块编号分别为171和174,t1段的第一个区间的起始编号为168,该区间由8个数据块组成。
下面在同一个数据块171上同时执行多个事务,看看到底会发生什么。
session1:
update scott.t1 set b = 'Oracle data' where a <= 10;
已更新10行。
session2:
update scott.t1 set b = 'Oracle data' where a > 10 and a <= 20;
已更新10行。
session3:
先确定当前会话的sid
select sid from v$mystat where rownum = 1;
SID
----------
136
update scott.t1 set b = 'Oracle data' where a > 20 and a <= 30;
操作被hang住,事务处于等待状态。
查看会话的等待事件
col event for a30
select sid, event, seconds_in_wait, state from v$session_wait where sid = 136;
SID EVENT SECONDS_IN_WAIT STATE
---------- ------------------------------ --------------- -------------------
136 enq: TX - allocate ITL entry 242 WAITING
此时出现了分配ITL条目的等待。因为默认的初始ITL槽位分配为2,而pctfree为0,两个事务不提交,block中就没有足够空间分配ITL了,因此出现了会话被hang住一直在等待ITL的分配。前面的会话提交或回滚后,后面的会话才得以执行。
四、ITL进一步研究
当一个事务完成时,Oracle需要执行块清理(block cleanout),清理掉这些在数据块上的事务数据,清除ITL中的标志位、行中row header中的标志位等。块清理分为两种:fast commit block cleanout和deferred block cleanout。
快速提交块清理(fast commit block cleanout):这是Oracle的默认行为。
延迟块清理(deferred block cleanout):事务提交时,Oracle仅简单的更新相关回滚段的头部信息,而把数据块的清理操作留给后来需要读写这个数据块的操作者(之后的事务)。
设想一个update大量数据的操作,因为执行时间较长,一部分已修改的块已被缓冲池flush out写至磁盘,当update操作完成执行commit操作时,为进行块清理,需要将那些已经写至磁盘的数据块重新读入,这将消耗大量I/O,并使commit操作十分缓慢。为解决这个问题,Oracle使用了延迟块清理的方案,对待存在以下情况的块,commit操作不做块清理:
1、在更新过程中,被缓冲池flush out写至磁盘的块;
2、当更新操作涉及的块超过了块缓冲区缓存的10%时,超出部分的块。
虽然commit放弃对这些块的清理,但仍会修改回滚段的段头,回滚段的段头包括了段中的事务信息,commit操作将本事务转化为非active状态。
当下一次操作如select、update、insert或delete访问到这些块时再来完成对块的清理,这称之为延迟块清理。块延迟清除通过事务槽上的回滚段号、槽号等信息访问回滚段头的事务信息,若事务不再活跃或事务过期则完成块清理。块延迟清除的影响在select操作过程中体现的最为明显,这也是select语句产生redo信息的主要原因。
继续前面的案例,先执行一个更新启动一个新的事务,然后查询出此更新涉及的数据块,然后dump该数据块的内容,进一步验证ITL的信息。
执行更新
update scott.t1 set b = 'Oracle data' where a = 100;
确定更新所在的文件号和块号
select dbms_rowid.rowid_relative_fno(rowid) from scott.t1 where a = 100;
DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID)
------------------------------------
4
select dbms_rowid.rowid_block_number(rowid) from scott.t1 where a = 100;
DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------
171
开启会话跟踪
alter session set sql_trace=true;
oradebug setmypid
oradebug tracefile_name
c:\oracle\diag\rdbms\mes\mes\trace\mes_ora_3852.trc
dump数据块
alter system dump datafile 4 block 171;
查看跟踪文件c:\oracle\diag\rdbms\mes\mes\trace\mes_ora_3852.trc,可以看到关于ITL的信息:
Block header dump: 0x010000ab
Object id on Block? Y
seg/obj: 0x12458 csc: 0x00.1eb08d itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x10000a8 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0007.015.0000040c 0x00c00618.017a.07 C-U- 0 scn 0x0000.001ea944
0x02 0x0006.00e.000004ee 0x00c011d1.014e.1e ---- 1 fsc 0x0002.00000000
flag:事务状态标志,占用块中的一个字节,对应于v$transaction视图中的status字段,意义如下:
----:transaction is active or committed pending cleanout
c---:transaction has been committed and locks cleaned out
--u-:transaction committed(maybe long ago),SCN is an upper bound
-b--:this undo record contains the undo for this ITL entry
---t:transaction was still active at block cleanout SCN
SCN/fsc:该ITL对应的事务提交时的SCN,那么这里所有槽位上最大的一个SCN号就表示这个block最后被更新时的SCN。每一个事务对应一个ITL记录,如果该事务没有涉及延迟块清理,那么显示的是fsc,如果是延迟块清理,那么显示的就是SCN。
lck:事务锁影响的记录数。
对照视图v$transaction,获取此处update操作对应的事务信息
select xidusn, xidslot, xidsqn, ubafil, ubablk, ubasqn, ubarec from v$transaction;
XIDUSN XIDSLOT XIDSQN UBAFIL UBABLK UBASQN UBAREC
---------- ---------- ---------- ---------- ---------- ---------- ----------
6 14 1262 3 4561 334 30
xid:其构成是xidusn.xidslot.xidsqn,三部分信息分别表示
xidusn:undo segment number 回滚段号
xidslot:slot number 事务槽号
xidsqn:sequence number 序列号
uba:其构成是dba.ubasqn.ubarec,而dba包含了ubafil和ubablk的信息,分解如下
select dbms_utility.data_block_address_file(to_number('00c011d1', 'xxxxxxxx')) ubafil, dbms_utility.data_block_address_block(to_number('00c011d1', 'xxxxxxxx')) ubablk from dual;
UBAFIL UBABLK
---------- ----------
3 4561
ubafil:undo block address(uba) filenum 回滚块地址 - 文件号
ubablk:undo block number 回滚块地址 - 块号
ubasqn:uba sequence number 回滚块地址 - 序列号
ubarec:uba record number 回滚块地址 - 记录号
于是根据uba信息,可以从回滚段的数据块中找到该项事务的回滚信息,为此可以dump回滚段的数据块
alter system dump datafile 3 block 4561;
查看跟踪文件信息,找到该事务对应的回滚信息
UNDO BLK:
xid: 0x0006.00e.000004ee seq: 0x14e cnt: 0x1e irb: 0x1e icl: 0x0 flg: 0x0000
这里的seq即序列号ubasqn,cnt即记录号ubarec。由rec #0x1e可以进一步在trace文件中找到update前的回滚信息
* Rec #0x1e slt: 0x0e objn: 74840(0x00012458) objd: 74840 tblspc: 4(0x00000004)
* Layer: 11 (Row) opc: 1 rci 0x1d
Undo type: Regular undo Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000
*-----------------------------
KDO undo record:
KTB Redo
op: 0x02 ver: 0x01
compat bit: 4 (post-11) padding: 0
op: C uba: 0x00c011d1.014e.1c
KDO Op code: ORP row dependencies Disabled
xtype: XA flags: 0x00000000 bdba: 0x010000ab hdba: 0x010000aa
itli: 2 ispac: 0 maxfr: 4858
tabn: 0 slot: 99(0x63) size/delt: 11
fb: --H-FL-- lb: 0x2 cc: 2
null: --
col 0: [ 2] c2 02
col 1: [ 4] 64 61 74 61
fb:行标记,H表示head of row,F和L分别表示行的first piece和last piece,说明此行涉及导出的数据块,不存在行链接,又由于块中存在行头,说明也存在行迁移。
lb:ITL事务槽编号
cc:列的数量
回滚前的编码是64 61 74 61,转换为原始字符信息
select chr(to_number(64, 'xx')) || chr(to_number(61, 'xx')) || chr(to_number(74, 'xx')) || chr(to_number(61, 'xx')) undo_data from dual;
UNDO_DAT
--------
data
以上测试可见,Oracle是通过数据块中的ITL信息来找到事务对应的回滚信息,同时实现了事务的读一致性。如果事务已完成,ITL就可以被重用。
五、ITL与CR块
Oracle的锁管理是一种轻量级的锁定机制,不是通过构建锁列表来进行数据锁定管理的,而是直接将锁作为数据块的属性存储在数据块头部,通过ITL实现。一个事务要修改块中的数据,必须获得改块中的一个ITL(通过initrans预先分配的或者是通过pctfree space后来构建的),通过ITL和undo segment header中的transaction table,可以知道事务处于活动阶段还是已经完成。事务在修改块时会检查row header中的标志位,如果该标志位为0(该行没有被活动的事务锁住,这是可能要进行延迟块清除等工作),就把该标志位修改为事务在该块获得的ITL序号,这样当前事务就获得了对记录的锁定,然后就可以修改行数据了。与此同时,在该事务处理过程中,如果有会话查询该数据块中的数据,Oracle就会读取回滚段中的内容来构造保障数据读一致性的CR(consistent read)块。
在多用户并发环境下,一个数据块可以有多个CR版本,Oracle会在下列情况下构造数据块的CR版本:
1、如果一个数据块上有锁,而有会话需要读取这个数据块中的内容,Oracle就会构造该数据块的CR版本;
2、是否需要构造CR块,与SCN密切相关。如果一个查询游标对应的SCN小于数据块当前的SCN,此时Oracle需要构造对应查询游标SCN的CR块。
以下看一下数据块及其不同版本的例子,操作分别在几个不同会话中进行。
session1:
查出表中数据所在的文件号和块号
select distinct dbms_rowid.rowid_relative_fno(rowid) file#, dbms_rowid.rowid_block_number(rowid) block# from scott.emp;
FILE# BLOCK#
---------- ----------
4 151
由文件号和块号查询缓存中的数据块,此时还没有该数据块信息
select file#, block#, status, dirty, objd, ts# from v$bh where file# = 4 and block# = 151;
未选定行
对数据做查询操作
select * from scott.emp;
缓存中产生了数据块的xcur版本
select file#, block#, status, dirty, objd, ts# from v$bh where file# = 4 and block# = 151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 xcur N 73196 4
刷新缓存
alter system flush buffer_cache;
缓存中的数据块没有消失,但状态变为了free版本
select file#, block#, status, dirty, objd, ts# from v$bh where file# = 4 and block# = 151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 free N 73196 4
对数据再次做查询操作
select * from scott.emp;
查询缓存块,此时多了一个xcur版本
select file#, block#, status, dirty, objd, ts# from v$bh where file#=4 and block#=151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 free N 73196 4
4 151 xcur N 73196 4
session2:
对数据块进行更新操作,但不提交
update scott.emp set sal = 1000 where empno =7369;
session1:
查询缓存块,dirty列标志为Y,表示为脏数据
select file#, block#, status, dirty, objd, ts# from v$bh where file#=4 and block#=151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 free N 73196 4
4 151 xcur Y 73196 4
再次对数据做查询操作
select * from scott.emp;
查询缓存块,又多了一个cr版本,因为之前session2的更新没有提交,所以session1查询时,通过读取回滚段在buffer cache中构建了CR块。
select file#, block#, status, dirty, objd, ts# from v$bh where file#=4 and block#=151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 free N 73196 4
4 151 xcur Y 73196 4
4 151 cr N 73196 4
session2:
提交更新
commit;
session1:
再次对数据做查询操作
select * from scott.emp;
查询缓存块,状态信息不变,因为是否提交并不影响数据块内容
select file#, block#, status, dirty, objd, ts# from v$bh where file#=4 and block#=151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 free N 73196 4
4 151 xcur Y 73196 4
4 151 cr N 73196 4
session2:
再次更新数据,但不提交
update scott.emp set sal = 800 where empno =7369;
session1:
对数据做查询操作
select * from scott.emp;
查询缓存块,可以看到又新构建了一个CR版本
select file#, block#, status, dirty, objd, ts# from v$bh where file#=4 and block#=151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 free N 73196 4
4 151 xcur Y 73196 4
4 151 cr N 73196 4
4 151 cr N 73196 4
session2:
提交更新
commit;
session1:
对数据做查询操作
select * from scott.emp;
查询缓存块,状态不改变
select file#, block#, status, dirty, objd, ts# from v$bh where file#=4 and block#=151;
FILE# BLOCK# STATUS D OBJD TS#
---------- ---------- ---------- - ---------- ----------
4 151 free N 73196 4
4 151 xcur Y 73196 4
4 151 cr N 73196 4
4 151 cr N 73196 4
数据块在缓存中的状态及其物理意义如下:
free:not currently in use
xcur:exclusive
scur:shared current
cr:consistent read
read:begin read from disk
mrec:in media recovery mode
irec:in instance recovery mode
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/28974745/viewspace-2146505/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/28974745/viewspace-2146505/