1. 程式人生 > >DeadLock in Oracle Database

DeadLock in Oracle Database

死鎖

在事務執行高峰期,由於DDL、DML使用鎖資源十分頻繁,爭用時甚至會發生死鎖,阻塞的鎖可能導致系統延遲,甚至長時間不響應,影響業務系統的正常執行。若死鎖頻頻發生,則有可能是應用設計不當。這裡暫且初步整理下鎖爭用的有關資料。

1.鎖介紹

概述

鎖:併發訪問、更新時容易出現

概念:當資料庫物件正在被其他程序或使用者修改時,可以保護它不被修改

結構:“排隊”的佇列,先到先服務,序列工作

功能:

  • 堅持一致性和完整性
  • 佇列結構管理請求的會話
  • 自動處理鎖機制
  • 鎖的持續時間等於被提交事務的長度或處理時間

型別

DDL

  • 在修改過程中保護模式物件
  • DDL鎖,由Oracle自動釋出和釋放

DML

  • 在事務處理過程中保護物件
  • 當釋出rollback或commit時,認為完成了事務

內部鎖

  • 由Oracle管理,以保護內部資料庫結構,如資料檔案

模式

鎖模式描述 …

鎖模式和DML語句 …

鎖模式和DDL語句 …

級別

資料庫級別

鎖定資料庫以禁止任何新會話和新事務,可以分為以下三種類型。

ALTER SYSTEM ENABLE RESTRICTED SESSION

* 使資料庫進入限制模式
* 僅有RESTRICTED SESSION許可權使用者才能登陸
* 已經登陸的會話不受此印象
* 登陸的使用者可以正常進行DDL/DML操作

ALTER SYSTEM QUIESCE RESTRICTED

* 使資料庫進入靜默的限制模式
* 從使用者的活動中鎖定資料庫,鎖定所有操作(處於等待狀態),直到UNQUICESCE
* 不允許非特權使用者登陸
* 特權使用者可以正常進行操作

ALTER DATABASE OPEN READ ONLY

* 使資料庫以只讀模式開啟
* UNDO段處於離線狀態
* 不允許任何更新、事務操作
表級別
  • 通過DML或者LOCK語句發出
行級別
  • DML更新一行或多行時
  • Oracle支援的最低級別
  • SELECT … FOR UPDATE只鎖定返回的行
列級別
  • Oracle不支援

鎖語句

LOCK語法

 LOCK TABLE
   [ schema.]{table | view} [@dblink]
   [, [schema.]{table | view} [@dblink] ] ...
 IN lockmode MODE
   [ NOWAIT]

示例

* SHARE(S)
* ROW SHARE(RS)
* ROW EXCLUSIVE(RX)
* SHARE ROW EXCLUSIVE(SRX)
* EXCLUSIVE(X)
* SELECT ... FOR UPDATE

2.死鎖日誌

警告日誌

Wed Feb 27 09:34:58 2013
ORA-000060: Deadlock detected. More info in file /u1/PROD/prodora/proddb/9.2.0/admin/PROD_erpprod/udump/prod_ora_4513878.trc.

跟蹤日誌

... ...
*** 2013-02-11 03:01:37.846
*** SESSION ID:(25.18) 2013-02-11 03:01:37.846
Undo Segment 11 Onlined 
*** 2013-02-11 08:41:27.866 
Undo Segment 19 Onlined 
*** 2013-02-16 08:46:16.482
Undo Segment 134 Onlined
*** 2013-02-16 09:55:31.314
Undo Segment 195 Onlined
*** 2013-02-27 09:34:58.033
DEADLOCK DETECTED
Current SQL statement for this session:
UPDATE FND_CONCURRENT_REQUESTS SET PP_END_DATE = SYSDATE, POST_REQUEST_STATUS = 'E' WHERE REQUEST_ID = :B1 
----- PL/SQL Call Stack -----
  object      			line  		object
  handle    			number  	name
700000095f756d0        		67  	package body APPS.FND_CP_OPP_CMD
70000008caf2ea0         	1  		anonymous block
The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:
Deadlock graph:
                       ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TX-0159001e-000075b3        24      25     X             24      25           X
session 25: DID 0001-0018-00000006	session 25: DID 0001-0018-00000006
Rows waited on:
Session 25: obj - rowid = 00009050 - AAAJBQAAmAAAKPzAAG
  (dictionary objn - 36944, file - 38, block - 41971, slot - 6)
Information on the OTHER waiting sessions:
End of information on OTHER waiting sessions.
... ...

3.死鎖情形模擬

若在請求鎖資源時出現迴圈等待,則產生死鎖現象。如下,以列出順序先後執行更新操作。

1.獲取並保持鎖,如LOCK1,且為排他鎖,鎖定更新行1:

SQL(49,180)>update dt_char set name = '1A' where id = 1;

1 row updated.

2.獲取並保持鎖,如LOCK2,且為排他鎖,鎖定更新行2:

SQL(462,13)> update dt_char set name = '2B' where id = 2;

1 row updated.

3.請求更新行2,因處於等待,等待鎖LOCK2

SQL(49,180)>update dt_char set name = '2A' where id = 2;

4.請求更新行1,因處於等待,等待鎖LOCK1

SQL(462,13)> update dt_char set name = '1B' where id = 1;

此時,會話(49,180)和(462,13)在鎖資源LOCK1和LOCK2上存在迴圈等待的情形,故出現死鎖,3中的語句終止;此時4中的語句處於等待狀態。

SQL(49,180)>update dt_char set name = '2A' where id = 2;
update dt_char set name = '2A' where id = 2
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource

出現死鎖時,系統會

4.死鎖解決方案

ORA-00060 deadlock detected while waiting for resource

Cause: Your session and another session are waiting for a resource locked by the other. This condition is known as a deadlock. To resolve the deadlock, one or more statements were rolled back for the other session to continue work.

Action: Either:

  • Enter a ROLLBACK statement and re-execute all statements since the last commit or
  • Wait until the lock is released, possibly a few minutes, and then re-execute the rolled back statements.

除了執行ROLLBACK,或者等待鎖資源釋放,並重新執行所需要的語句外;還有一種方案就是清理掉死鎖中阻塞的一方的資料庫會話。

--生成kill session指令碼,殺掉死鎖中阻塞一方的會話
--ALTER SYSTEM KILL SESSION 'sid,serial#';
SELECT 'alter system kill session ''' || SID || ',' || SERIAL# || ''';' "Deadlock"
  FROM V$SESSION
 WHERE SID IN (SELECT SID FROM V$LOCK WHERE BLOCK = 1);

DDL Lock

DDL語句在釋出時為獲取資料物件的內部結構而需要排他鎖,如果鎖不可用則會更新失敗。

1.直接釋出ALTER TABLE語句

建立SQL*Plus會話(27,0),發出ALTER TABLE語句:

SQL> SELECT * FROM V$MYSTAT WHERE ROWNUM = 1;

       SID STATISTIC#	   VALUE
---------- ---------- ----------
	27	    0	       0

SQL> desc dt_char;
 Name					   Null?    Type
 ----------------------------------------- -------- ----------------------------
 NAME						    CHAR(10)

SQL> alter table dt_char modify (name varchar(20));

Table altered.

SQL> desc dt_char;
 Name					   Null?    Type
 ----------------------------------------- -------- ----------------------------
 NAME						    VARCHAR2(20)

2.有DML更新表資料時釋出DDL語句

預設

新開一個SQL*Plus會話(513,22540),發出UPDATE語句:

SQL> SELECT * FROM V$MYSTAT WHERE ROWNUM = 1;

       SID STATISTIC#	   VALUE
---------- ---------- ----------
	513	    22540	       0

SQL> update dt_char set name = 'DDL Lock';

0 rows updated.

此時,在會話(27,0)中再次釋出ALTER TABLE語句:

SQL> alter table dt_char modify (name char(10));
alter table dt_char modify (name char(10))
            *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired

此時,提示要求更新的表結構正處於繁忙狀態(被其他會話佔用),因不願意等待鎖釋放或者等待超時,故更新失敗。

檢視哪些物件處於爭用之中:

SQL> SELECT lo.session_id,
   s.serial#,
   lo.oracle_username,
   o.owner,
   o.object_name,
   o.object_type,
   decode(lo.locked_mode,
          0,
          'None', /* Mon Lock equivalent */
          1,
          'Null', /* N */
          2,
          'Row-S (SS)', /* L */
          3,
          'Row-X (SX)', /* R */
          4,
          'Share', /* S */
          5,
          'S/Row-X (SSX)', /* C */
          6,
          'Exclusive', /* X */
          lo.locked_mode) locked_mode
  	FROM v$locked_object lo,
       dba_objects     o,
       v$session       s
 WHERE lo.object_id = o.object_id
   AND s.sid = lo.session_id;
SESSION_ID SERIAL# ORACLE_USERNAME OWNER OBJECT_NAME OBJECT_TYPE LOCKED_MODE
---------- ---------- ---------------- ---------- ------------ ------------ -----------
	513			22540 DEV			   DEV		  DT_CHAR	   TABLE		Row-X (SX)

從以上查詢可以看出,表DEV.DT_CHAR正在被會話(513.22540)佔用,鎖模式為Row-X,即排他鎖,為了更新,在某個事務中鎖定了行,則不允許其他事務鎖定這個表。

在這個例子中,釋出的DML後,已經獲取了鎖,並鎖定了表DEV.DT_CHAR,因沒有釋出ROLLBACK、COMMIT,該會話一直持有鎖資源。

指定TIMEOUT

在會話(27,0)中,釋出超時為30s:

[email protected](27,0)> alter session set ddl_lock_timeout=30;		--DDL_LOCK_TIMEOUT引數值預設為0

Session altered.

[email protected](27,0)> alter table dt_char modify (name char(10));	--處於等待狀態,超時30s

此時,若在釋出DDL時起30s內,會話(513,22540)釋放鎖資源,則DDL可以更新成功:

[email protected](513,22540)> commit;

Commit complete.

[email protected](27,0)> alter table dt_char modify (name char(10));	--處於等待狀態,超時30s

Table altered.

否則,DDL失敗:

[email protected](27,0)> alter table dt_char modify (name char(10));	--處於等待狀態,超時30s
alter table dt_char modify (name char(10))
            *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired

DML Lock

1.事務和儲存點

原始資料:

SQL> select * from dt_char;

NAME
----------
1
2
3
4
5

更新行:

SQL> update dt_char set name = '1A' where name = '1';

1 row updated.

SQL> select * from dt_char;

NAME
----------
1A
2
3
4
5

建立儲存點,用來控制事務

SQL> savepoint A;

Savepoint created.

更新行:

SQL> update dt_char set name = '2A' where name = '2';

1 row updated.

SQL> select * from dt_char;

NAME
----------
1A
2A
3
4
5

回滾到儲存點A,此時在A之前的鎖沒有釋放,在A之後的鎖則被釋放,更新將會回滾到儲存A(若不指定儲存點,則回滾到更新的初始狀態):

SQL> rollback to savepoint A;

Rollback complete.

SQL> select * from dt_char;

NAME
----------
1A
2
3
4
5

常用指令碼

查詢當前會話ID

--查詢當前會話ID
SELECT * FROM V$MYSTAT WHERE ROWNUM = 1;

查詢會話鎖

--查詢會話鎖
SELECT * FROM DBA_LOCKS WHERE SESSION_ID IN (513);

檢測鎖爭用

--request:如果request值非0,則表示在等待一個鎖
--block:如果block為1,則表示此會話持有一個鎖,並阻塞別人獲得此鎖
SELECT *
  FROM V$LOCK
 WHERE BLOCK = 1
    OR REQUEST > 0;

被鎖定的物件

--被鎖定的物件
SELECT * FROM V$LOCKED_OBJECT;

僅列出使用者所保持的鎖

--僅列出使用者所保持的鎖
SELECT S.USERNAME,
       L.SESSION_ID,
       L.LOCK_TYPE,
       L.MODE_HELD,
       L.MODE_REQUESTED,
       L.LOCK_ID1,
       L.LOCK_ID2,
       L.LAST_CONVERT,
       L.BLOCKING_OTHERS
  FROM V$SESSION S, DBA_LOCKS L
 WHERE S.USERNAME IS NOT NULL
   AND (S.SID = L.SESSION_ID AND L.MODE_REQUESTED != 'NONE')
    OR (S.SID = L.SESSION_ID AND L.MODE_REQUESTED = 'NONE' AND
       L.MODE_HELD != 'Share' AND
       (LOCK_ID1, LOCK_ID2) IN
       (SELECT A.LOCK_ID1, A.LOCK_ID2
           FROM DBA_LOCKS A
          WHERE A.MODE_REQUESTED != 'NONE'
            AND A.LOCK_ID1 = L.LOCK_ID1
            AND A.LOCK_ID2 = L.LOCK_ID2))
 ORDER BY 6, 7, 5;

哪些物件處於爭用中

--v$locked_object
--哪些物件處於爭用中
SELECT LO.SESSION_ID,
       S.SERIAL#,
       LO.ORACLE_USERNAME,
       S.PROCESS,
       S.PROGRAM,
       O.OWNER,
       O.OBJECT_NAME,
       O.OBJECT_TYPE,
       DECODE(LO.LOCKED_MODE,
              0,
              'None', /* Mon Lock equivalent */
              1,
              'Null', /* N */
              2,
              'Row-S (SS)', /* L */
              3,
              'Row-X (SX)', /* R */
              4,
              'Share', /* S */
              5,
              'S/Row-X (SSX)', /* C */
              6,
              'Exclusive', /* X */
              LO.LOCKED_MODE) LOCKED_MODE
  FROM V$LOCKED_OBJECT LO, DBA_OBJECTS O, V$SESSION S
 WHERE LO.OBJECT_ID = O.OBJECT_ID
   AND S.SID = LO.SESSION_ID
   AND S.SID IN (336, 1360);

死鎖檢測

--dba_blockers
--阻塞其他使用者的會話的id
SELECT * FROM DBA_BLOCKERS;

--dba_waiters
--顯示等待將被阻塞會話釋放鎖的會話id
SELECT * FROM DBA_WAITERS;

--使用dba_waiters檢視
資訊格式:Blocker|Waiter(sid,serial#,username,sql)
SELECT 'Blocker(' || BW.HOLDING_SESSION || ':' || SB.USERNAME ||
       ') - SQL: ' || BQ.SQL_TEXT BLOCKERS,
       'Waiter(' || BW.WAITING_SESSION || ':' || SW.USERNAME || ') - SQL: ' ||
       WQ.SQL_TEXT BLOCKERS
  FROM DBA_WAITERS BW,
       V$SESSION   SB,
       V$SESSION   SW,
       V$SQLAREA   BQ,
       V$SQLAREA   WQ
 WHERE BW.HOLDING_SESSION = SB.SID
   AND BW.WAITING_SESSION = SW.SID
   AND SB.PREV_SQL_ADDR = BQ.ADDRESS
   AND SW.SQL_ADDRESS = WQ.ADDRESS
   AND BW.MODE_HELD <> 'None';

--使用dba_locks檢視
--資訊格式:Blocker|Waiter(sid,serial#,username,sql)
SELECT DISTINCT 'Blocker(' || LB.SESSION_ID || ',' || SB.SERIAL# || ':' ||
                SB.USERNAME || ') - SQL: ' || BQ.SQL_TEXT BLOCKERS,
                'Waiter(' || LW.SESSION_ID || ',' || SW.SERIAL# ||
                SW.USERNAME || ') - SQL: ' || WQ.SQL_TEXT BLOCKERS
  FROM DBA_LOCKS LB,
       V$SESSION SB,
       DBA_LOCKS LW,
       V$SESSION SW,
       V$SQL     BQ,
       V$SQL     WQ
 WHERE LB.SESSION_ID = SB.SID
   AND LW.SESSION_ID = SW.SID
   AND SB.PREV_SQL_ADDR = BQ.ADDRESS
   AND SW.SQL_ADDRESS = WQ.ADDRESS
   AND LB.LOCK_ID1 = LW.LOCK_ID1
   AND SW.LOCKWAIT IS NOT NULL
   AND SB.LOCKWAIT IS NULL
   AND LB.BLOCKING_OTHERS = 'Blocking';

生成kill session指令碼

--生成kill session指令碼,殺掉死鎖中阻塞一方的會話
--ALTER SYSTEM KILL SESSION 'sid,serial#';
SELECT 'alter system kill session ''' || SID || ',' || SERIAL# || ''';' "Deadlock"
  FROM V$SESSION
 WHERE SID IN (SELECT SID FROM V$LOCK WHERE BLOCK = 1);

檢視導致死鎖的SQL

--導致死鎖的SQL 
SELECT S.SID, Q.SQL_TEXT
  FROM V$SQLTEXT Q, V$SESSION S
 WHERE Q.ADDRESS = S.SQL_ADDRESS
   AND S.SID = &SID
 ORDER BY PIECE;

延伸閱讀

Reference

  • Database Error Message
  • Oracle 9i資料庫效能優化與調整
blog comments powered by Disqus