【体系结构】ITL阻塞与ITL死锁

一、什么是ITL

  ITL(Interested Transaction List)是Oracle数据块内部的一个组成部分,用来记录该块所有发生的事务,一个ITL可以看作是一个记录,在一个时间,可以记录一个事务(包括提交或者未提交事务)。当然,如果这个事务已经提交,那么这个ITL的位置就可以被反复使用了,因为ITL类似记录,所以,有的时候也叫ITL槽位。

  如果一个事务一直没有提交,那么,这个事务将一直占用一个ITL槽位,ITL里面记录了事务信息,回滚段的入口,事务类型等等。如果这个事务已经提交,那么,ITL槽位中还保存的有这个事务提交时候的SCN号。如dump一个块,就可以看到ITL信息:

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0001.008.00000308  0x00000000.0000.00  C---    0  scn   0x00000000002dd4fc
0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc   0x0000.00000000
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc   0x0000.00000000

  对于已经提交的事务,ITL槽最好不要被马上覆盖,因为一致性读可能会用到这个信息,一致性读的时候,可能需要从这里获得回滚段的入口,并从回滚段中获得一致性读。

  ITL的个数,受参数initrans控制,最大的ITL个数,受maxtrans控制,在一个块内部,默认分配了2个或3个ITL的个数,如果这个块内还有空闲空间,那么Oracle是可以利用这些空闲空间并再分配ITL的。如果没有了空闲空间,那么,这个块因为不能分配新的ITL,所以就可能发生ITL等待。

  如果在并发量特别大的系统中,最好分配足够的ITL个数,其实它并浪费不了太多的空间,或者,设置足够的pctfree,保证ITL能扩展,但是pctfree有可能是被行数据给消耗掉的,如update,所以,也有可能导致块内部的空间不够而导致ITL等待。

 

二、ITL等待

  我们看一个ITL等待的例子:

SQL> create table test(id number) pctfree 0 initrans 1 tablespace test;

Table created.

这里制定pctfree为0,initrans为1,就是为了更好观察到ITL的真是等待情况,现在我们向表中插入数据,把块填满,让它不能有空间分配

SQL> begin
  2  for i in 1..2000 loop
  3  insert into test values(i);
  4  end loop;
  5  commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

检查数据填充情况

SQL> select f,b,count(*) from (select dbms_rowid.rowid_relative_fno(rowid) f,dbms_rowid.rowid_block_number(rowid) b  from test) group by f,b;

         F          B   COUNT(*)
---------- ---------- ----------
        22        235        733
        22        238        733
        22        229        534

可以发现,2000条数据分布在3个块中,其中有两个块填满了,一个块使用了一半。dump出这个块,查看ITL信息

SQL> alter system dump datafile 22 block 230;

System altered.

在dump目录下,检查跟踪文件,可以看到如下信息

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0009.007.00000303  0x02401405.00fb.4b  --U-  733  fsc 0x0000.00319fb4
0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

发现采用上述参数创建的表,块内部默认有两个ITL槽位,如果不指定initrans 1,默认是有3个ITL槽位的

因为只有2个ITL槽位,可以使用第三个会话来模拟等待

会话1:更新块内部的第一行

SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=238 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1;

1 row updated.

会话2:更新块内部的第二行

SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=238 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;

1 row updated.

会话3:更新这个块第三行

SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=238 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;

可以看到会话被阻塞

观察这个时候的等待事件,发现是ITL等待:

SQL> select EVENT from v$session_wait where sid=73;

EVENT
----------------------------------------------------------------
enq: TX - allocate ITL entry

  因为该块只有2个ITL槽位,而现在发生了3个事务,而且,因为该块被数据添满,根本没有剩余的空间来分配新的ITL,所以发生了等待。如果我们这个实验发生在半满的块231上面,就发现进程3不会被阻塞,因为这里有足够的空间可以分配新的ITL。

 

三、ITL死锁

  因为有阻塞,一般就能发生死锁。还是以上的表,因为只有两个ITL槽位,需要拿两个块填满的数据库,4个进程来模拟ITL死锁。

------会话1:
update
test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=238 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1; ------会话2: update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=238 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2; ------会话3: update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=235 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1; ------会话4: update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=235 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;

以上4个进程把2个不同块的4个ITL槽位给消耗光了,现在的情况,就是让他们互相锁住,达成死锁条件,回到会话1,更新块2,注意,以上4个操作,包括以下的操作,更新的根本不是同一行数据,主要是为了防止出现的是TX等待。

SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=235 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;

被阻塞,那我们在会话3,更新块1,当然也不是同一行

SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=238 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;

同样被阻塞

如果是9i,这里在会话1中就会看到报错死锁

ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource

但是,9i之后,这种情况,死锁就不会发生,因为这里进程1还可以等待进程4释放资源,进程3还可以等待进程2释放资源,只要进程2、4释放了资源,整个环境就又活了,那么我们把这两个进程也塞住

------会话2:被阻塞
SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=235 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=4;
------会话4:被阻塞
SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=238 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=4;

虽然以上的每个更新语句,更新的都不是同一个数据行,但是,的确,所有的进程都被阻塞住了,那么,死锁的条件也达到了,马上,我们可以看到,进程1出现提示,死锁:

SQL> update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=235 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
update test set id=1 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=235 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3
                *
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource

 

四、ITL等待与死锁的避免

  为了避免以上的情况发生,我们一定要注意在高并发环境下的表中,正确的设置ITL个数,如4个,8个等等,保证该块有足够的ITL槽位,保证事务能顺利的进行,而没有itl的等待。关于ITL的等待,在AWR的段报告中,也能很明显的看到:

Top 10 Foreground Events by Total Wait Time

Event                          Waits   Total Wait Time (sec)    Avg Wait    % DB time    Wait Class
enq: TX - allocate ITL entry   123     2183.1                   17.75 s     99.8         Configuration

  如果出现的频率很小,像上面的情况,一般可以不用干预,但是,如果waits很多,则表示这个对象有很严重的ITL争用情况,需要增加ITL个数。

  另外注意的是,有ITL等待,并不意味会发生ITL死锁,从上面的例子可以看到,发生ITL死锁的条件还是很苛刻的,如果发生了ITL死锁,只能证明,你的系统中,ITL等待已经非常严重了。

  如果想增加initrans个数,参数可以动态修改,但是,只是针对以后的新块起效,以前的块如果想生效,需要在新参数下,重整表数据,如重建该表,或者move该表。

 

posted @ 2021-08-02 15:04  蟹Bro  阅读(469)  评论(0编辑  收藏  举报