遭遇ITL死锁

转载自www.ixdba.com:

一个增量刷新的程序,看哪些记录发生变化了(使用TRIGGER记录),就在远端把相应的记录删除,再把本地的数据insert过去。因为数据量比较大,而单一的delete和insert对资源的使用率也不高,就准备多开几个程序一起刷新,来加快刷新速度。其中有一个中间表,只会插入和删除,不会更新,就想当然的把PCTFREE设置为0以节省空间。结果发生了死锁。piner版主对这种类型的死锁进行了深入的分析,这里只进行转载。

[@more@]

1、什么是ITL

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

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

    Itl           Xid                  Uba         Flag  Lck        Scn/Fsc0x01   0x0006.002.0000158e  0x0080104d.00a1.6e  --U-  734  fsc 0x0000.6c9deff00x02   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等待。

2、ITL等待

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

  1. Piner@10gR2>create table test(a int) pctfree 0 initrans 1;
  2. Table created.

我们这里指定pctfree为0,initrans为1,就是为了更观察到itl的真实等待情况,那么,现在,我们个这些块内插入数据,把块填满,让它不能有空间分配。

  1. Piner@10gR2>begin
  2. 2 for i in 1..2000 loop
  3. 3 insert into test values(i);
  4. 4 end loop;
  5. 5 end;
  6. 6 /
  7. PL/SQL procedure successfully completed.
  8. Piner@10gR2>commit;
  9. Commit complete.

我们再检查数据填充的情况:

  1. Piner@10gR2>select f,b,count(*) from (
  2. 2 select dbms_rowid.rowid_relative_fno(rowid) f,
  3. 3 dbms_rowid.rowid_block_number(rowid) b
  4. 4 from test) group by f,b;
  5. F B COUNT(*)
  6. ---------- ---------- ----------
  7. 1 29690 734
  8. 1 29691 734
  9. 1 29692 532

可以发现,这2000条数据分布在3个块内部,其中有2个块添满了,一个块是半满的。我们dump一个满的块,可以看到itl信息:

  1. Piner@10gR2>alter system dump datafile 1 block 29690;

回到os,在udump目录下,检查跟踪文件,可以看到如下的信息

    Itl           Xid                  Uba         Flag  Lck        Scn/Fsc0x01   0x0006.002.0000158e  0x0080104d.00a1.6e  --U-  734  fsc 0x0000.6c9deff00x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

发现,采用如上参数创建的表,块内部默认有2个itl槽位,如果这里不指定initrans 1,默认是有3个itl槽位的。

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

会话1,我们更新这个块内部的第一行:

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1;
  4. 1 row updated.

会话2,我们更新这个块内部的第2行:

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;
  4. 1 row updated.

会话3(SID=153),我们更新这个块内部的第三行,发现被阻塞:

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;

可以看到,会话被阻塞

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

  1. Piner@10gR2>select EVENT from v$session_wait where sid=153
  2. EVENT
  3. ----------------------------
  4. enq: TX - allocate ITL entry

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

3、ITL死锁

那么,理解了itl的阻塞,我们也就可以分析itl的死锁了,因为有阻塞,一般就能发生死锁。还是以上的表,因为有2个itl槽位,我们需要拿2个满的数据块,4个进程来模拟itl死锁:

会话1

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1;
  4. 1 row updated.

会话2

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;
  4. 1 row updated.

会话3

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1;
  4. 1 row updated.

会话4

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;
  4. 1 row updated.

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

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;

发现被阻塞

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

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;

被阻塞

注意,如果是9i,在这里就报死锁了,在进程1,我们可以看到

  1. Piner@9iR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
  4. update test set a=a
  5. where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  6. and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3
  7. *
  8. ERROR at line 1:
  9. ORA-00060: deadlock detected while waiting for resource

但是,在10g里面,这个时候,死锁是不会发生的,因为这里的进程1还可以等待进程4释放资源,进程3还可以等待进程2释放资源,只要进程2与进程4释放了资源,整个环境又活了,那么我们需要把这两个进程也塞住。

会话2,注意,我们也不是更新的同一行数据

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=4;

被阻塞

还有最后一个进程,进程4,我们也不更新同一行数据

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=4;

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

  1. Piner@10gR2>update test set a=a
  2. 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  3. 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
  4. update test set a=a
  5. where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
  6. and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3
  7. *
  8. ERROR at line 1:
  9. ORA-00060: deadlock detected while waiting for resource

4、ITL等待与死锁的避免

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

    Top 5 ITL Waits per Segment for DB: TEST  Instance: test  Snaps: 13013 -13014-> End Segment ITL Waits Threshold:       100Subobject  Obj.           ITLOwner      Tablespace Object Name          Name       Type         Waits  %Total---------- ---------- -------------------- ---------- ----- ------------ -------TEST       TBS_EL_IND IDX_LLORDER_ORDERID             INDEX            3   75.00TEST       TBS_INDEX2 IDX_AUC_FEED_FDATE              INDEX            1   25.00

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

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

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

附上当时发生死锁的trace文件中的部分:

last wait for 'enq: TX - allocate ITL entry' blocking sess=0x0x10da2a450 seq=34603 wait_time=2930318 secon
ds since wait started=9
name|mode=54580004, usn<<16 | slot=b0000, sequence=6832
Dumping Session Wait History
for 'enq: TX - allocate ITL entry' count=1 wait_time=2930318
name|mode=54580004, usn<<16 | slot=b0000, sequence=6832
for 'enq: TX - allocate ITL entry' count=1 wait_time=2930265
name|mode=54580004, usn<<16 | slot=b0000, sequence=6832
for 'enq: TX - allocate ITL entry' count=1 wait_time=2930266
name|mode=54580004, usn<<16 | slot=b0000, sequence=6832
for 'enq: TX - allocate ITL entry' count=1 wait_time=1954238
name|mode=54580004, usn<<16 | slot=e0028, sequence=4032
for 'latch: enqueue hash chains' count=1 wait_time=40565
address=10ded34e0, number=13, tries=0
for 'enq: TX - allocate ITL entry' count=1 wait_time=2932664
name|mode=54580004, usn<<16 | slot=e0028, sequence=4032
for 'db file sequential read' count=1 wait_time=5001
file#=6, block#=43545, blocks=1
for 'db file sequential read' count=1 wait_time=6264
file#=1a, block#=48462, blocks=1
for 'SQL*Net message from dblink' count=1 wait_time=579
driver id=d202844, #bytes=1, =0
for 'SQL*Net message to dblink' count=1 wait_time=1
driver id=d202844, #bytes=1, =0

由此再结合PCTFREE的设置,确定是碰到了ITL竞争锁,在把表进行重建,并加大PCTFREE后,问题得到解决。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/25016/viewspace-922310/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/25016/viewspace-922310/


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部