1. 程式人生 > 其它 >外來鍵與死鎖

外來鍵與死鎖

技術標籤:資料庫oracle

要分析外來鍵無索引可能會導致死鎖問題,需要先了解Oracle的幾種表鎖,參考官方文件

  • A table lock, also called a TM lock, is acquired by a transaction when a table is modified by an INSERT, UPDATE, DELETE, MERGE, SELECT with the FOR UPDATE clause, or LOCK TABLE statement. DML operations require table locks to reserve DML access to the table on behalf of a transaction and to prevent DDL operations that would conflict with the transaction.

    A table lock can be held in any of the following modes:

    • Row Share (RS)

      This lock, also called a subshare table lock (SS), indicates that the transaction holding the lock on the table has locked rows in the table and intends to update them. A row share lock is the least restrictive mode of table lock, offering the highest degree of concurrency for a table.

    • Row Exclusive Table Lock (RX)

      This lock, also called a subexclusive table lock (SX), generally indicates that the transaction holding the lock has updated table rows or issued SELECT ... FOR UPDATE. An SX lock allows other transactions to query, insert, update, delete, or lock rows concurrently in the same table. Therefore, SX locks allow multiple transactions to obtain simultaneous SX and subshare table locks for the same table.

    • Share Table Lock (S)

      A share table lock held by a transaction allows other transactions to query the table (without using SELECT ... FOR UPDATE), but updates are allowed only if a single transaction holds the share table lock. Because multiple transactions may hold a share table lock concurrently, holding this lock is not sufficient to ensure that a transaction can modify the table.

    • Share Row Exclusive Table Lock (SRX)

      This lock, also called a share-subexclusive table lock (SSX), is more restrictive than a share table lock. Only one transaction at a time can acquire an SSX lock on a given table. An SSX lock held by a transaction allows other transactions to query the table (except for SELECT ... FOR UPDATE) but not to update the table.

    • Exclusive Table Lock (X)

      This lock is the most restrictive, prohibiting other transactions from performing any type of DML statement or placing any type of lock on the table.

幾種表鎖模式翻譯如下:

  • 行共享(RS)

    此鎖也稱為子共享表鎖 (SS),表示在表上持有鎖的事務已鎖定表中的行,並打算更新它們。行共享鎖是表鎖限制最少的模式,為表提供最高程度的併發性。

  • 行獨佔表鎖(RX)

    此鎖也稱為子排他性表鎖(SX),通常表示持有該鎖的事務已更新錶行或發出 SELECT…FOR UPDATE。SX 鎖允許其他事務在同一表中同時查詢、插入、更新、刪除或鎖定行。因此,SX 鎖允許多個事務同時獲取同一表的 SX 和子共享表鎖 (SS)。

  • 共享表鎖(S)

    事務持有的共享表鎖允許其他事務查詢表(不使用 SELECT …FOR UPDATE),但僅在單個事務持有共享表鎖時才允許更新。由於多個事務可能同時持有共享表鎖,因此持有此鎖不足以確保事務可以修改表。

  • 共享行獨佔表鎖(SRX)

    此鎖也稱為共享子排他表鎖(SSX),比共享表鎖更具限制性。一次只有一個事務可以在給定的表上獲取 SSX 鎖。事務持有的 SSX 鎖允許其他事務查詢表(除了SELECT…FOR UPDATE),但無法更新表。

  • 獨佔表鎖(X)

    此鎖是限制性最強的,禁止其他事務執行任何型別的 DML 語句或將任何型別的鎖放在表上。

對於鎖和外來鍵,參考官方文件:

Locks and Foreign Keys

Oracle Database maximizes the concurrency control of parent keys in relation to dependent foreign keys.

Locking behavior depends on whether foreign key columns are indexed. If foreign keys are not indexed, then the child table will probably be locked more frequently, deadlocks will occur, and concurrency will be decreased. For this reason foreign keys should almost always be indexed. The only exception is when the matching unique or primary key is never updated or deleted.

Locks and Unindexed Foreign Keys

The database acquires a full table lock on the child table when no index exists on the foreign key column of the child table, and a session modifies a primary key in the parent table (for example, deletes a row or modifies primary key attributes) or merges rows into the parent table.

When both of the following conditions are true, the database acquires a full table lock on the child table:

  • No index exists on the foreign key column of the child table.
  • A session modifies a primary key in the parent table (for example, deletes a row or modifies primary key attributes) or merges rows into the parent table.

就是說如果在子表的外來鍵上面沒有建立索引,對父表主鍵的刪除或者修改操作將會鎖住整個子表。

外來鍵無索引

我們知道外來鍵無索引有可能會導致死鎖,下面來看看具體是怎麼產生的。

先建立測試表:

SQL> create table emp as select * from employees;

Table created.

SQL> create table dept as select * from departments;

Table created.

SQL> alter table dept modify department_id primary key;

Table altered.

SQL> alter table emp add constraint fx_emp_deptid foreign key(department_id) references dept(department_id);

Table altered.

在會話1執行基於外來鍵的刪除子表記錄的操作:

SQL> select userenv('sid') from dual;

USERENV('SID')
--------------
           197

SQL> delete from emp where department_id=10;

1 row deleted.

在會話3檢視鎖情況:

SELECT S.SID         SID,
       L.TYPE        TYPE,
       O.OBJECT_NAME OBJECT_NAME,
       DECODE(L.LMODE, 0, 'None', 
                       1, 'Null', 
                       2, 'Row Share', 
                       3, 'Row Exlusive', 
                       4, 'Share', 
                       5, 'Share Row Exlusive', 
                       6, 'Exclusive')   lmode, 
       DECODE(L.REQUEST, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Share Row Exlusive', 
                         6, 'Exclusive') request, 
       L.BLOCK       BLOCK,
       S.BLOCKING_SESSION,
       S.BLOCKING_SESSION_STATUS
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O
 WHERE L.SID = S.SID
   AND USERNAME != 'SYSTEM'
   AND O.OBJECT_ID(+) = L.ID1
   AND S.SID IN (197, 131)
   AND L.TYPE NOT IN ('AE')
 ORDER BY S.SID;

       SID TY OBJECT_NAME          LMODE              REQUEST                 BLOCK BLOCKING_SESSION BLOCKING_SE
---------- -- -------------------- ------------------ ------------------ ---------- ---------------- -----------
       197 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER
       197 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM EMP                  Row Exlusive       None                        0                  NO HOLDER

在會話2執行基於外來鍵的刪除子表記錄的操作:

SQL> select userenv('sid') from dual;

USERENV('SID')
--------------
           131

SQL> delete from emp where department_id=20;

2 rows deleted.

在會話3檢視鎖情況:

SELECT S.SID         SID,
       L.TYPE        TYPE,
       O.OBJECT_NAME OBJECT_NAME,
       DECODE(L.LMODE, 0, 'None', 
                       1, 'Null', 
                       2, 'Row Share', 
                       3, 'Row Exlusive', 
                       4, 'Share', 
                       5, 'Share Row Exlusive', 
                       6, 'Exclusive')   lmode, 
       DECODE(L.REQUEST, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Share Row Exlusive', 
                         6, 'Exclusive') request, 
       L.BLOCK       BLOCK,
       S.BLOCKING_SESSION,
       S.BLOCKING_SESSION_STATUS
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O
 WHERE L.SID = S.SID
   AND USERNAME != 'SYSTEM'
   AND O.OBJECT_ID(+) = L.ID1
   AND S.SID IN (197, 131)
   AND L.TYPE NOT IN ('AE')
 ORDER BY S.SID;
 
       SID TY OBJECT_NAME          LMODE              REQUEST                 BLOCK BLOCKING_SESSION BLOCKING_SE
---------- -- -------------------- ------------------ ------------------ ---------- ---------------- -----------
       131 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER
       131 TM EMP                  Row Exlusive       None                        0                  NO HOLDER
       131 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM EMP                  Row Exlusive       None                        0                  NO HOLDER
       197 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER

在會話1執行基於主鍵的刪除父表記錄的操作:

SQL> delete from dept where department_id=10;

此時該操作被阻塞,在會話3檢視鎖情況:

SELECT S.SID         SID,
       L.TYPE        TYPE,
       O.OBJECT_NAME OBJECT_NAME,
       DECODE(L.LMODE, 0, 'None', 
                       1, 'Null', 
                       2, 'Row Share', 
                       3, 'Row Exlusive', 
                       4, 'Share', 
                       5, 'Share Row Exlusive', 
                       6, 'Exclusive')   lmode, 
       DECODE(L.REQUEST, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Share Row Exlusive', 
                         6, 'Exclusive') request, 
       L.BLOCK       BLOCK,
       S.BLOCKING_SESSION,
       S.BLOCKING_SESSION_STATUS
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O
 WHERE L.SID = S.SID
   AND USERNAME != 'SYSTEM'
   AND O.OBJECT_ID(+) = L.ID1
   AND S.SID IN (197, 131)
   AND L.TYPE NOT IN ('AE')
 ORDER BY S.SID;
 
       SID TY OBJECT_NAME          LMODE              REQUEST                 BLOCK BLOCKING_SESSION BLOCKING_SE
---------- -- -------------------- ------------------ ------------------ ---------- ---------------- -----------
       131 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER
       131 TM EMP                  Row Exlusive       None                        1                  NO HOLDER
       131 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM EMP                  Row Exlusive       Share Row Exlusive          1              131 VALID
       197 TX                      Exclusive          None                        0              131 VALID
       197 TM DEPT                 Row Exlusive       None                        0              131 VALID

可以看到會話1(SID=197)請求EMP表上的Share Row Exlusive,被會話2(SID=131)上的Row Exlusive阻塞。

在會話2執行基於主鍵的刪除父表記錄的操作:

SQL> delete from dept where department_id=20;

此時會話1就會檢測到死鎖:

SQL> delete from dept where department_id=10;
delete from dept where department_id=10
            *
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource

再在會話3檢視鎖情況:

SELECT S.SID         SID,
       L.TYPE        TYPE,
       O.OBJECT_NAME OBJECT_NAME,
       DECODE(L.LMODE, 0, 'None', 
                       1, 'Null', 
                       2, 'Row Share', 
                       3, 'Row Exlusive', 
                       4, 'Share', 
                       5, 'Share Row Exlusive', 
                       6, 'Exclusive')   lmode, 
       DECODE(L.REQUEST, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Share Row Exlusive', 
                         6, 'Exclusive') request, 
       L.BLOCK       BLOCK,
       S.BLOCKING_SESSION,
       S.BLOCKING_SESSION_STATUS
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O
 WHERE L.SID = S.SID
   AND USERNAME != 'SYSTEM'
   AND O.OBJECT_ID(+) = L.ID1
   AND S.SID IN (197, 131)
   AND L.TYPE NOT IN ('AE')
 ORDER BY S.SID;
 
       SID TY OBJECT_NAME          LMODE              REQUEST                 BLOCK BLOCKING_SESSION BLOCKING_SE
---------- -- -------------------- ------------------ ------------------ ---------- ---------------- -----------
       131 TM DEPT                 Row Exlusive       None                        0              197 VALID
       131 TM EMP                  Row Exlusive       Share Row Exlusive          1              197 VALID
       131 TX                      Exclusive          None                        0              197 VALID
       197 TM EMP                  Row Exlusive       None                        1                  NO HOLDER
       197 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER 

外來鍵有索引

給外來鍵加上索引:

SQL> create index idx_emp_deptid on emp(department_id);

Index created.

在會話1執行基於外來鍵的刪除子表記錄的操作:

SQL> delete from emp where department_id=10;

1 row deleted.

在會話3檢視鎖情況:

SELECT S.SID         SID,
       L.TYPE        TYPE,
       O.OBJECT_NAME OBJECT_NAME,
       DECODE(L.LMODE, 0, 'None', 
                       1, 'Null', 
                       2, 'Row Share', 
                       3, 'Row Exlusive', 
                       4, 'Share', 
                       5, 'Share Row Exlusive', 
                       6, 'Exclusive')   lmode, 
       DECODE(L.REQUEST, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Share Row Exlusive', 
                         6, 'Exclusive') request, 
       L.BLOCK       BLOCK,
       S.BLOCKING_SESSION,
       S.BLOCKING_SESSION_STATUS
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O
 WHERE L.SID = S.SID
   AND USERNAME != 'SYSTEM'
   AND O.OBJECT_ID(+) = L.ID1
   AND S.SID IN (197, 131)
   AND L.TYPE NOT IN ('AE')
 ORDER BY S.SID; 

       SID TY OBJECT_NAME          LMODE              REQUEST                 BLOCK BLOCKING_SESSION BLOCKING_SE
---------- -- -------------------- ------------------ ------------------ ---------- ---------------- -----------
       197 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER
       197 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM EMP                  Row Exlusive       None                        0                  NO HOLDER 

在會話2執行基於外來鍵的刪除子表記錄的操作:

SQL> delete from emp where department_id=20;

2 rows deleted.

在會話3檢視鎖情況:

SELECT S.SID         SID,
       L.TYPE        TYPE,
       O.OBJECT_NAME OBJECT_NAME,
       DECODE(L.LMODE, 0, 'None', 
                       1, 'Null', 
                       2, 'Row Share', 
                       3, 'Row Exlusive', 
                       4, 'Share', 
                       5, 'Share Row Exlusive', 
                       6, 'Exclusive')   lmode, 
       DECODE(L.REQUEST, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Share Row Exlusive', 
                         6, 'Exclusive') request, 
       L.BLOCK       BLOCK,
       S.BLOCKING_SESSION,
       S.BLOCKING_SESSION_STATUS
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O
 WHERE L.SID = S.SID
   AND USERNAME != 'SYSTEM'
   AND O.OBJECT_ID(+) = L.ID1
   AND S.SID IN (197, 131)
   AND L.TYPE NOT IN ('AE')
 ORDER BY S.SID;
 
       SID TY OBJECT_NAME          LMODE              REQUEST                 BLOCK BLOCKING_SESSION BLOCKING_SE
---------- -- -------------------- ------------------ ------------------ ---------- ---------------- -----------
       131 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER
       131 TM EMP                  Row Exlusive       None                        0                  NO HOLDER
       131 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM EMP                  Row Exlusive       None                        0                  NO HOLDER
       197 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER 

在會話1執行基於主鍵的刪除父表記錄的操作:

SQL> delete from dept where department_id=10;

1 row deleted.

此時該操作沒有被阻塞,在會話3檢視鎖情況:

SELECT S.SID         SID,
       L.TYPE        TYPE,
       O.OBJECT_NAME OBJECT_NAME,
       DECODE(L.LMODE, 0, 'None', 
                       1, 'Null', 
                       2, 'Row Share', 
                       3, 'Row Exlusive', 
                       4, 'Share', 
                       5, 'Share Row Exlusive', 
                       6, 'Exclusive')   lmode, 
       DECODE(L.REQUEST, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Share Row Exlusive', 
                         6, 'Exclusive') request, 
       L.BLOCK       BLOCK,
       S.BLOCKING_SESSION,
       S.BLOCKING_SESSION_STATUS
  FROM V$LOCK L, V$SESSION S, DBA_OBJECTS O
 WHERE L.SID = S.SID
   AND USERNAME != 'SYSTEM'
   AND O.OBJECT_ID(+) = L.ID1
   AND S.SID IN (197, 131)
   AND L.TYPE NOT IN ('AE')
 ORDER BY S.SID;
 

       SID TY OBJECT_NAME          LMODE              REQUEST                 BLOCK BLOCKING_SESSION BLOCKING_SE
---------- -- -------------------- ------------------ ------------------ ---------- ---------------- -----------
       131 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER
       131 TM EMP                  Row Exlusive       None                        0                  NO HOLDER
       131 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM EMP                  Row Exlusive       None                        0                  NO HOLDER
       197 TX                      Exclusive          None                        0                  NO HOLDER
       197 TM DEPT                 Row Exlusive       None                        0                  NO HOLDER 

這裡可以看到會話1(SID=197)沒有請求EMP表上的Share Row Exlusive。

在會話2執行基於主鍵的刪除父表記錄的操作:

SQL> delete from dept where department_id=20;

1 row deleted.

沒有被阻塞。

歡迎關注我的公眾號,一起學習。