遭遇ITL死鎖
轉載自:
一個增量重新整理的程式,看哪些記錄發生變化了(使用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等待的例子:
- Piner@10gR2>create table test(a int) pctfree 0 initrans 1;
- Table created.
我們這裡指定pctfree為0,initrans為1,就是為了更觀察到itl的真實等待情況,那麼,現在,我們個這些塊內插入資料,把塊填滿,讓它不能有空間分配。
- Piner@10gR2>begin
- 2 for i in 1..2000 loop
- 3 insert into test values(i);
- 4 end loop;
- 5 end;
- 6 /
- PL/SQL procedure successfully completed.
- Piner@10gR2>commit;
- Commit complete.
我們再檢查資料填充的情況:
- Piner@10gR2>select f,b,count(*) from (
- 2 select dbms_rowid.rowid_relative_fno(rowid) f,
- 3 dbms_rowid.rowid_block_number(rowid) b
- 4 from test) group by f,b;
- F B COUNT(*)
- ---------- ---------- ----------
- 1 29690 734
- 1 29691 734
- 1 29692 532
可以發現,這2000條資料分佈在3個塊內部,其中有2個塊添滿了,一個塊是半滿的。我們dump一個滿的塊,可以看到itl資訊:
- 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,我們更新這個塊內部的第一行:
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1;
- 1 row updated.
會話2,我們更新這個塊內部的第2行:
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;
- 1 row updated.
會話3(SID=153),我們更新這個塊內部的第三行,發現被阻塞:
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
可以看到,會話被阻塞
觀察這個時候的等待事件,我們可以發現是ITL等待:
- Piner@10gR2>select EVENT from v$session_wait where sid=153
- EVENT
- ----------------------------
- enq: TX - allocate ITL entry
因為該塊只有2個itl槽位,而現在發生了3個事務,而且,因為該塊被資料添滿,根本沒有剩餘的空間來分配新的itl,所以發生了等待。如果我們這個實驗發生在半滿的塊29692上面,就發現程式3不會被阻塞,因為這裡有足夠的空間可以分配新的itl。
3、ITL死鎖
那麼,理解了itl的阻塞,我們也就可以分析itl的死鎖了,因為有阻塞,一般就能發生死鎖。還是以上的表,因為有2個itl槽位,我們需要拿2個滿的資料塊,4個程式來模擬itl死鎖:
會話1
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1;
- 1 row updated.
會話2
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;
- 1 row updated.
會話3
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=1;
- 1 row updated.
會話4
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=2;
- 1 row updated.
以上4個程式把2個不同塊的4個itl槽位給消耗光了,現在的情況,就是讓他們互相鎖住,達成死鎖條件,回到會話1,更新塊2,注意,以上4個操作,包括以下的操作,更新的根本不是同一行資料,主要是為了防止出現的是TX等待。
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
發現被阻塞
那我們在會話3,更新塊1,當然,也不是同一行
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
被阻塞
注意,如果是9i,在這裡就報死鎖了,在程式1,我們可以看到
- Piner@9iR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
- update test set a=a
- where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3
- *
- ERROR at line 1:
- ORA-00060: deadlock detected while waiting for resource
但是,在10g裡面,這個時候,死鎖是不會發生的,因為這裡的程式1還可以等待程式4釋放資源,程式3還可以等待程式2釋放資源,只要程式2與程式4釋放了資源,整個環境又活了,那麼我們需要把這兩個程式也塞住。
會話2,注意,我們也不是更新的同一行資料
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=4;
被阻塞
還有最後一個程式,程式4,我們也不更新同一行資料
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29690
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=4;
雖然,以上的每個更新語句,更新的都不是同一個資料行,但是,的確,所有的程式都被阻塞住了,那麼,死鎖的條件也達到了,馬上,我們可以看到,程式1出現提示,死鎖:
- Piner@10gR2>update test set a=a
- 2 where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- 3 and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3;
- update test set a=a
- where dbms_rowid.ROWID_BLOCK_NUMBER(rowid)=29691
- and dbms_rowid.ROWID_ROW_NUMBER(rowid)=3
- *
- ERROR at line 1:
- 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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 深入研究ITL阻塞與ITL死鎖 作者 piner
- 推薦:深入研究ITL阻塞與ITL死鎖(轉載)
- enq: TX - allocate ITL entry等待過多導致全域性死鎖ENQ
- 【故障處理】佇列等待之TX - allocate ITL entry引起的死鎖處理佇列
- Java鎖——死鎖Java
- 死鎖
- SQ死鎖及死鎖的解決
- 面試官:什麼是死鎖?怎麼排查死鎖?怎麼避免死鎖?面試
- 什麼是死鎖?如何解決死鎖?
- 殺死Oracle死鎖程式Oracle
- 死鎖分析
- oracle 死鎖Oracle
- 檢視oracle死鎖程式並結束死鎖Oracle
- 例項詳解 Java 死鎖與破解死鎖Java
- MySQL死鎖系列-線上死鎖問題排查思路MySql
- 作業系統(5) 死鎖的概念 死鎖產生的必要條件 死鎖的處理策略 預防死鎖 避免死鎖 死鎖的檢測和解除 銀行家演算法作業系統演算法
- MySQL 死鎖和鎖等待MySql
- GCD 死鎖原因GC
- 死鎖案例分析
- HashMap死鎖分析HashMap
- SQL Server死鎖SQLServer
- 死鎖-舉例
- MySQL:一個死鎖分析 (未分析出來的死鎖)MySql
- mysql行鎖和死鎖檢測MySql
- SQLServer的死鎖分析(1):頁鎖SQLServer
- Mysql 兩階段鎖和死鎖MySql
- MySQL 死鎖解決MySql
- PostgreSQL死鎖相關SQL
- Java 中的死鎖Java
- MySQL死鎖問題MySql
- PostgreSQL 死鎖異常SQL
- 【MySQL】漫談死鎖MySql
- 淺談死鎖原理
- 批量刪除死鎖
- Oracle 死鎖處理Oracle
- Oracle死鎖處理Oracle
- ORACLE死鎖檢測Oracle
- 搭建死鎖場景