推薦:深入研究ITL阻塞與ITL死鎖(轉載)

zyb200發表於2007-05-28
作者: (轉載請註明本文出處:)
永久連結:
[@more@]

首先要說的是,這個問題當初我是真遇到過,然後,就有了汪海的這篇文章:

但是NinGoo 在下面回覆說,10g模擬不到這樣的情況了,開始我沒有太在意,後來再想想,9i這麼做的確是不對的,因為還有其它的程式可以釋放資源,根本還沒有達到死鎖的條件。那麼,10g就沒有itl死鎖了嗎?也不是的,10g也有,不過是改進了一下,需要把所有的程式阻塞住的時候,才能爆發出死鎖。

從死鎖的原理上來看,10g是正確的,9i是欠完善的。我們現在完整的分析一下itl等待,以及itl死鎖的前因後果,因為這部分在我的新書中也有涉及,屬於比較難的一部分,先透露出來,免得大家到時候看書可能看得比較糊塗。

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該表。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/64429/viewspace-916504/,如需轉載,請註明出處,否則將追究法律責任。

相關文章