【IT168 技术文档】 我们知道,事务在对数据块中的记录加锁时,需要首先在数据块头部记录下该事务的相关信息,这样一个记录就是一条ITL槽(slot)。ITL TX等待发生在事务请求对数据块中记录加锁时,数据块上没有足够ITL槽。
导致发生ITL不足的原因有3种:
INITRANS太小,没有保留足够的ITL空间,当数据块被数据记录占满(或接近满)后,数据块上没有足够空间创建新的ITL槽位(尽管数据块上ITL数量没有达到MAXTRANS的限制),这时,新的事务向其申请加锁时,就会发生ITL争用等待。
注:尽管有PCTFREE为数据块预留了空间,但是,UPDATE操作可能会将其占用,导致空间不足。
注2:INITRANS不足的问题不会出现在索引数据块上,当发现没有足够空间分配ITL slot时,无论是枝点块还是叶子块,数据块会发生分裂(Index Block Split)。
MAXTRANS不足。当受到MAXTRANS限制时,也会发生此类等待。MAXTRANS的默认值是255(10g以后则不能修改这一参数),但是其实际大小是受到块的大小的限制的。ITL Slot占用的空间不会超过块大小的50%(实际上,如数据块为2K,则ITL最多为41,4k数据块的ITL最大数为83,8K数据块则为169)。
索引块上的递归事务的ITL slot争用。这一类等待比较特殊。在索引的枝节点上,有且只有一个ITL slot,它是用于当发生节点分裂的递归事务(Recursive Transaction)。在叶子节点上,第一条ITL Slot也是用于分裂的递归事务的。在一个用户事务中,如果发生多次分裂,每一次分裂都是由一个单独的递归事务控制的,如果下层节点分裂导致其父节点分裂,它们的分裂则由同一个递归事务控制。当2个事务同时需要分裂一个枝节点或者叶子节点时,或者枝节点下的2个子节点分别被2个事务分裂,就会造成这种ITL等待。
下面的代码模拟第一种情形导致的TX锁(继续使用之前的表进行演示)——表创建时,INITRANS默认值为1:
HELLODBA.COM> create index tx_lock_tab_idx on tx_lock_tab (c);
index created.
HELLODBA.COM> begin
2 for i in 1..5000 loop
3 insert into tx_lock_tab (a, b, c) values(i, 'E', lpad('A', 8, 'A'));
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
--将一个数据块的记录大小增大,填充PTCFREE留下的空闲空间
HELLODBA.COM> update tx_lock_tab set c=lpad('A', 10, 'A') where a between 3633 and 3995;
363 rows updated.
HELLODBA.COM> update tx_lock_tab set c=lpad('A', 60, 'A') where a=3633;
1 row updated.
HELLODBA.COM> commit;
Commit complete.
通过dump出数据块,可以看到数据块上空闲空间已经极少了:
fsbo=0x2e8
fseo=0x30d
0x30d - 0x2e8 = 25d
...
仅够容纳1个ITL了(24字节),加上数据块上原有了2条ITL slot,这个数据块上只能容纳最多3个事务:
HELLODBA.COM> update tx_lock_tab set c=lpad('1',10,'1') where a=3635;
1 row updated.
-- 注意:这条语句与ITL等待无直接关系,只是加入的一个干扰因素
HELLODBA.COM> update t_test5 set username='AAA' where user_id=1;
1 row updated.
--Session 2:
HELLODBA.COM> update tx_lock_tab set c=lpad('1',10,'1') where a=3636;
1 row updated.
HELLODBA.COM> update t_test5 set username='AAA' where user_id=2;
1 row updated.
--Session 3:
HELLODBA.COM> update tx_lock_tab set c=lpad('1',10,'1') where a=3637;
1 row updated.
HELLODBA.COM> update t_test5 set username='AAA' where user_id=3;
1 row updated.
--第四个事务被阻塞:
HELLODBA.COM> update tx_lock_tab set c=lpad('1',10,'1') where a=3638;