外來鍵欄位未建索引引發的死鎖

zhang41082發表於2019-05-17

現象:一個很簡單的程式在壓力測試過程中發現死鎖,檢視trace檔案,發現如下資訊:

Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TM-0000627d-00000000 21 1062 SX SSX 22 1092 SX SSX
TM-0000627d-00000000 22 1092 SX SSX 21 1062 SX SSX
session 1062: DID 0001-0015-0000001E session 1092: DID 0001-0016-0005C757
session 1092: DID 0001-0016-0005C757 session 1062: DID 0001-0015-0000001E
Rows waited on:
Session 1092: no row
Session 1062: no row
而引發這個死鎖的sql如下:
Current SQL Statement:
DELETE FROM TABLE WHERE COL = :B1

[@more@]

首先注意到的是Deadlock graph中的資源佔有情況,可以看到兩個session都hold了一個SZ型別的鎖,同時在等待SSX型別的鎖,而且引發的是一個刪除語句,並且這個表是系統的一個關鍵表,大部分的表的外來鍵都引用自此表的主鍵。因此猜測是碰到了外來鍵引發的死鎖。試驗如下:
1、建立一個表,此表作為子表
SQL> create table fk_table as select * from user_objects;

Table created
2、建立一個表,此表作為父表
SQL> create table pk_table as select * from user_objects;

Table created
3、建立父表的主鍵
SQL> alter table PK_TABLE add constraint pk_pktable primary key (OBJECT_ID);

Table altered
4、建立子表的外來鍵
SQL> alter table FK_TABLE add constraint fk_fktable foreign key (OBJECT_ID) references pk_table (OBJECT_ID);

Table altered
5、如下sql取自TOAD工具,用來顯示資料庫鎖的資訊
SELECT LK.SID,
SE.USERNAME,
SE.OSUSER,
SE.MACHINE,
DECODE(LK.TYPE,
'TX',
'Transaction',
'TM',
'DML',
'UL',
'PL/SQL User Lock',
LK.TYPE) LOCK_TYPE,
DECODE(LK.LMODE,
0,
'None',
1,
'Null',
2,
'Row-S (SS)',
3,
'Row-X (SX)',
4,
'Share',
5,
'S/Row-X (SSX)',
6,
'Exclusive',
TO_CHAR(LK.LMODE)) MODE_HELD,
DECODE(LK.REQUEST,
0,
'None',
1,
'Null',
2,
'Row-S (SS)',
3,
'Row-X (SX)',
4,
'Share',
5,
'S/Row-X (SSX)',
6,
'Exclusive',
TO_CHAR(LK.REQUEST)) MODE_REQUESTED,
TO_CHAR(LK.ID1) LOCK_ID1,
TO_CHAR(LK.ID2) LOCK_ID2,
OB.OWNER,
OB.OBJECT_TYPE,
OB.OBJECT_NAME,
LK.BLOCK,
SE.LOCKWAIT
FROM V$LOCK LK, DBA_OBJECTS OB, V$SESSION SE
WHERE LK.TYPE IN ('TM', 'UL')
AND LK.SID = SE.SID
AND LK.ID1 = OB.OBJECT_ID(+);

6、執行一個刪除操作,這時候在子表和父表上都加了一個Row-S(SX)鎖
delete from fk_table where object_id=94716;
delete from pk_table where object_id=94716;
7、執行另一個刪除操作,發現這時候第二個刪除語句等待
delete from fk_table where object_id=94702;
delete from pk_table where object_id=94702;
執行查詢語句,得到鎖資訊如下:
857 DML Row-S (SS) None 107220 0 BILL TABLE PK_TABLE 0 00000000D555A0E8
872 DML Row-X (SX) None 107220 0 BILL TABLE PK_TABLE 0
857 DML Row-X (SX) S/Row-X (SSX) 107219 0 BILL TABLE FK_TABLE 0 00000000D555A0E8
872 DML Row-X (SX) None 107219 0 BILL TABLE FK_TABLE 1
可以看到session 857在請求一個SSX型別的鎖,此時如果執行如下操作:
delete from pk_table where object_id=94716;
死鎖馬上發生,因為857 SESSION拿到了一個對FK_TABLE的行獨佔鎖,並在請求一個表共享鎖,而872 SESSION也拿到了一個FK_TABLE上的行獨佔鎖,並請求一個表共享鎖。此時兩個session誰都不會釋放獨佔鎖,並同時請求表的共享鎖,死鎖由此引發。因為死鎖引發的時候兩個session不是在等待對資料行進行加鎖,所以可以從trace檔案中發現等待的行都為no row,同時可以看到兩個session都hold了一個SX鎖,並且都在等待SSX鎖資源。同時trace檔案中還記錄了引發死鎖的sql。

Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TM-0001a2d3-00000000 16 872 SX SSX 20 857 SX SSX
TM-0001a2d3-00000000 20 857 SX SSX 16 872 SX SSX
session 872: DID 0001-0010-000F5EA0 session 857: DID 0001-0014-001D7407
session 857: DID 0001-0014-001D7407 session 872: DID 0001-0010-000F5EA0
Rows waited on:
Session 857: no row
Session 872: no row

Current SQL Statement:
delete from pk_table where object_id=94716
8、當對子表的外來鍵列新增索引後,死鎖被消除,因為這時刪除父表記錄不需要對子表加表級鎖,這裡不再做測試。

結論:曾經有人討論過是否所有的資料庫設計都應該遵守正規化的規範,都把主外來鍵關係建立起來。也有人反對這樣做,因為這樣複雜的關係在OLTP系統中可能會成為災難,而提倡透過程式來保證資料的完整性,但程式發生bug導致資料不一致的情況時有發生。而且如果外來鍵設定為級聯刪除,則不加索引的外來鍵會使得對子表的記錄刪除走全表掃描。因此,對外來鍵的使用還是要慎重!

btw:為什麼刪除子表記錄的時候要加表級的共享鎖呢?還有這個加鎖好像只是一瞬間的,期待深入!!!



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

相關文章