問題背景
發生Oracle死鎖的多個行程執行的都是同一個儲存過程,大概程式碼及順序如下:
–1.首先透過主鍵order_no鎖住一條訂單
select t.* from order t where t.order_no=’order_no’ for update;
–2.其次透過主鍵channel_id鎖住一個渠道
select t.* from channel t where t.channel_id=’channel_id’ for update;
–3.然後透過主鍵order_no對訂單表資料進行修改
update order t set t.order_status=0,t.finish_time=sysdate where t.order_no=’order_no’;
commit;
死鎖情況描述
session A
–正在執行陳述句3,他處於enq: TX – allocate ITL entry等待
update order t set t.order_status=0,t.finish_time=sysdate where t.order_no=’orderno_a’;
session B
–正在執行陳述句2,他處於enq: TX – row lock contention等待
select t.* from channel t where t.channel_id=’ch1′ for update;
session C
正在執行陳述句2,他處於enq: TX – row lock contention等待
select t.* from channel t where t.channel_id=’ch1′ for update;
可能還會有更多的session處於執行陳述句2,並等待enq: TX – row lock contention的情況,這裡暫時只列3個session,其實2個也夠了,也能形成,只是機率很低。
等待鏈
A被C堵塞,C被B堵塞,B被A堵塞
等待鏈分析:
A執行到陳述句3了,說明主鍵為orderno_a的order資料行鎖和ch1的channel資料行鎖已經獲取到了,而其餘的B和C只能等待該ch1資料的行鎖釋放。
B和C都執行到陳述句2了,說明他們都獲取到了各自的order資料行鎖,且資料不是orderno_a鎖代表的資料。這點毋庸置疑。
疑問 :A,B,C操作的都是不同的訂單資料行,且都獲取到了各自的行鎖的,為什麼在表order上,還會發生A被C堵塞呢。
要知道為什麼有這個疑問,就要先明白,在A執行order的for update時是已經獲取了itl資源的,所以在後來真正update資料時是不應該存在這個等待的enq: TX – allocate ITL entry,因為他已經獲取這個資源了。
死鎖分析
要分析這個死鎖就要明白等待事件enq: TX – allocate ITL entry所代表的資源itl事務槽的含義。itl事務槽是資料塊頭中用來標記事務的記錄。在這裡有個重點是 資料塊 。想一想,如果 事務跨資料塊 了會怎樣。這就是這個死鎖的關鍵點。當然不同表的事務肯定跨資料塊了,一個事務即使修改一個表的多條資料也可能跨塊了。 這裡的情況是,order表上事務都是透過主鍵來操作的,對於一條資料,要跨越資料塊,行遷移或者行連線會有這種情況。
簡單說下這兩種情況
行遷移一般是update後經常出現,比如一個err_mesg欄位,初期只有10個字元,後面update為1000個字元,如果這個時候原資料塊找不下了,他就會找另外的資料塊來存放,而原資料塊上放一個新資料塊的dba(data block address),指向新的資料塊,如下圖: 行連線一般是insert時出現的,比如一條資料非常大,大到一個塊裝不下了,oracle會拆分成多個塊來存放。可以透過建立塊尺寸小的表空間來測試。
到此處, 要明白itl是資料塊上的資源,即使是同一個事務中,如果事務跨資料塊了,當他要修改這個資料塊時,他也需要重新再次在這個新塊上申請itl資源 ,也就是我這裡死鎖中,假設orderno_a資料rowid指向的塊為dba_1,行遷移中指向的塊為dba_2,在最開始for update時獲取的是塊dba_1中的itl資源,當最後真正update資料時,為了保護操作,需要獲取dba_2上的itl資源。而此時,其餘的很多session,比如B,C……N 等等session將塊dba_2上的itl資源耗盡了,那麼session A就處於等待資料塊dba_2上的itl資源的狀態,對應於enq: TX – allocate ITL entry。而其他session將等待session A釋放渠道表資料的鎖。完成了鎖的閉環
到此死鎖分析完畢。
測試
–建立order表,將PCTFREE置為0,INITRANS置為1create table t_order(mesg varchar2(4000)) PCTFREE 0 INITRANS 1;
–建立channel表
create table t_channel(id NUMBER);
–準備資料,對於order表,至少要有兩個塊有資料
–第一個塊的資料,有三條,即a,b,c
insert into t_order select rpad(‘a’,3000,’a’) from dual;
insert into t_itl select rpad(‘b’,1000,’b’) from dual;
insert into t_order select rpad(‘c’,3000,’c’) from dual;
–更改資料b,此時第一個塊裝不下,將會發生行遷移
update t_order set mesg=(select rpad(‘b’,3000,’b’) from dual) where mesg like ‘b%’;
–可以使用以下陳述句分析行遷移的表,只用作測試,線上生產慎用,可以dump第一個資料塊找到,遷移到哪一個dba去了
create table CHAINED_ROWS (
owner_name varchar2(30),
table_name varchar2(30),
cluster_name varchar2(30),
partition_name varchar2(30),
subpartition_name varchar2(30),
head_rowid rowid,
analyze_timestamp date
);
analyze table t_order list chained rows;
select * from CHAINED_ROWS;
–繼續插入資料,將遷移後的資料塊資料增加,方便之後for update時消耗這個塊的itl資源
–通常情況,下麵插入的資料就是放在b資料遷移後的資料塊的
insert into t_order select rpad(‘d’,1000,’d’) from dual;
insert into t_order select rpad(‘f’,6000,’f’) from dual;
insert into t_order select rpad(‘g’,300,’g’) from dual;
insert into t_order select rpad(‘h’,100,’h’) from dual;
/*開始模擬死鎖*/
–t1時刻
–session A
select * from t_order where mesg like ‘b%’ for update;
select * from t_channel where id=1 for update;
–t2時刻
–session B
select * from t_order where mesg like ‘d%’ for update;
select * from t_channel where id=1 for update;–等待session A 釋放
–其餘session
select * from t_order where mesg like ‘f%’ for update;
select * from t_channel where id=1 for update;–加入該條資料的行鎖等待
select * from t_order where mesg like ‘g%’ for update;
select * from t_channel where id=1 for update;–加入該條資料的行鎖等待
…..
/*如果這些資料不在b所在的塊,可以透過設定where條件為以下內容來指定更改b遷移後的塊
where DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) = ‘block_no’
and DBMS_ROWID.ROWID_ROW_NUMBER(ROWID) = 1;
–此時session B與其餘session將t_order的第二個塊,即d,f,g,h資料所在的塊的itl耗盡
–t3時刻
–session A 去更改t_order的資料
update t_order t set t.mesg=’bbbbb’ where t.mesg like ‘b%’;
–此時會等待session B及其他session釋放itl資源,而session B及其他session又在等待session A釋放channel的鎖
–形成了互相等待,閉環,死鎖形成
出處:linuxidc.com/
朋友會在“發現-看一看”看到你“在看”的內容