遭遇ITL死鎖

zhang41082發表於2019-06-30

轉載自:

一個增量重新整理的程式,看哪些記錄發生變化了(使用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/Fsc
    0x01   0x0006.002.0000158e  0x0080104d.00a1.6e  --U-  734  fsc 0x0000.6c9deff0
    0x02   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/Fsc
    0x01   0x0006.002.0000158e  0x0080104d.00a1.6e  --U-  734  fsc 0x0000.6c9deff0
    0x02   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:       100

                                               Subobject  Obj.           ITL
    Owner      Tablespace Object Name          Name       Type         Waits  %Total
    ---------- ---------- -------------------- ---------- ----- ------------ -------
    TEST       TBS_EL_IND IDX_LLORDER_ORDERID             INDEX            3   75.00
    TEST       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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章