【锁】Oracle死锁(DeadLock)的分类及其模拟
1 BLOG文档结构图
2 前言部分
2.1 导读和注意事项
各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~:
① 死锁的概念及其trace文件
② 死锁的分类
③ 行级死锁的模拟
④ ITL的概念、ITL结构
⑤ ITL引发的死锁处理
⑥ ITL死锁的模拟
Tips:
① 本文在itpub(http://blog.itpub.net/26736162)、博客园(http://www.cnblogs.com/lhrbest)和微信公众号(xiaomaimiaolhr)上有同步更新。
② 文章中用到的所有代码、相关软件、相关资料及本文的pdf版本都请前往小麦苗的云盘下载,小麦苗的云盘地址见:http://blog.itpub.net/26736162/viewspace-1624453/。
③ 若网页文章代码格式有错乱,请尝试以下办法:①去博客园地址阅读,②下载pdf格式的文档来阅读。
④ 在本篇BLOG中,代码输出部分一般放在一行一列的表格中。其中,需要特别关注的地方我都用灰色背景和粉红色字体来表示,比如在下边的例子中,thread 1的最大归档日志号为33,thread 2的最大归档日志号为43是需要特别关注的地方;而命令一般使用黄色背景和红色字体标注;对代码或代码输出部分的注释一般采用蓝色字体表示。
List of Archived Logs in backup set 11 Thrd Seq Low SCN Low Time Next SCN Next Time ---- ------- ---------- ------------------- ---------- --------- 1 32 1621589 2015-05-29 11:09:52 1625242 2015-05-29 11:15:48 1 33 1625242 2015-05-29 11:15:48 1625293 2015-05-29 11:15:58 2 42 1613951 2015-05-29 10:41:18 1625245 2015-05-29 11:15:49 2 43 1625245 2015-05-29 11:15:49 1625253 2015-05-29 11:15:53 [ZHLHRDB1:root]:/>lsvg -o T_XLHRD_APP1_vg rootvg [ZHLHRDB1:root]:/> 00:27:22 SQL> alter tablespace idxtbs read write; ====》2097152*512/1024/1024/1024=1G |
本文如有错误或不完善的地方请大家多多指正,ITPUB留言或QQ皆可,您的批评指正是我写作的最大动力。
2.2 本文简介
写了近大半年的书了,碰到了各种困难,不过幸运的是基本上都一一克服了。前段时间工作上碰到了一个很奇怪的死锁问题,由业务发出来的SQL来看是不太可能产生死锁的,不过的的确确实实在在的产生了,那作者是碰到了哪一类的死锁呢?ITL死锁!!有关当时的案例可以参考:http://blog.itpub.net/26736162/viewspace-2124771/和http://blog.itpub.net/26736162/viewspace-2124735/。于是,作者就把死锁可能出现的情况都分类总结了一下,分享给大家,欢迎大家指出错误。本文内容也将写入作者的新书中,欢迎大家提前订阅。
第二章 死锁(DeadLock)的分类及其模拟
1 死锁简介
1.1 什么是死锁?
所谓死锁,是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。Oracle对于“死锁”是要做处理的,而不是不闻不问。
[ZFLHRDB1:oracle]:/oracle>oerr ora 60 00060, 00000, "deadlock detected while waiting for resource" // *Cause: Transactions deadlocked one another while waiting for resources. // *Action: Look at the trace file to see the transactions and resources // involved. Retry if necessary. |
Cause: Your session and another session are waiting for are source 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: l. Enter arollback statement and re—execute all statements since the last commit or 2. Wait until the lock is released, possibly a few minutes, and then re—execute the rolled back statements.
1.2 死锁的trace文件
Oracle中产生死锁的时候会在alert告警日志文件中记录死锁的相关信息,无论单机还是RAC环境都有Deadlock这个关键词,而且当发生死锁时都会生成一个trace文件,这个文件名在alert文件中都有记载。由于在RAC环境中,是由LMD(Lock Manager Daemon)进程统一管理各个节点之间的锁资源的,所以,RAC环境中trace文件是由LMD进程来生成的。
在RAC环境中,告警日志的形式如下所示:
Mon Jun 20 10:10:56 2016 Global Enqueue Services Deadlock detected. More info in file /u01/app/oracle/diag/rdbms/raclhr/raclhr2/trace/raclhr2_lmd0_19923170.trc. |
在单机环境中,告警日志的形式如下所示:
Mon Jun 20 12:10:56 2016 ORA-00060: Deadlock detected. More info in file /u01/app/oracle/diag/rdbms/dlhr/dlhr/trace/dlhr_ora_16973880.trc. |
通常来讲,对于单机环境,当有死锁发生后,在trace文件中会看到如下的日志信息:
图2-1 单机环境下的死锁
当看到trace文件时,需要确认一下产生锁的类型,是两行还是一行,是TX还是TM,如果只有一行那么说明是同一个SESSION,可能是自治事务引起的死锁。
对于RAC环境,当有死锁发生后,在trace文件中会看到如下的日志信息:
图2-2 RAC环境下的死锁
1.3 死锁的检测时间
死锁的监测时间是由隐含参数_lm_dd_interval来控制的,在Oracle 11g中,隐含参数_lm_dd_interval的值默认为10,而在Oracle 10g中该参数默认为60,单位为秒。
SYS@oraLHRDB2> SELECT A.INDX, 2 A.KSPPINM NAME, 3 A.KSPPDESC, 4 B.KSPPSTVL 5 FROM X$KSPPI A, 6 X$KSPPCV B 7 WHERE A.INDX = B.INDX 8 AND LOWER(A.KSPPINM) LIKE LOWER('%&PARAMETER%'); ENTER VALUE FOR PARAMETER: _lm_dd_interval OLD 8: AND LOWER(A.KSPPINM) LIKE LOWER('%&PARAMETER%') NEW 8: AND LOWER(A.KSPPINM) LIKE LOWER('%_LM_DD_INTERVAL%')
INDX NAME KSPPDESC KSPPSTVL ---------- ------------------ ------------------------------ -------------------- 578 _lm_dd_interval dd time interval in seconds 10 |
可以看到该隐含参数的值为10。
2 死锁的分类
有人的地方就有江湖,有资源阻塞的地方就可能有死锁。Oralce中最常见的死锁分为:行级死锁(Row-Level Deadlock)和块级死锁(Block-Level Deadlock),其中,行级死锁分为①主键、唯一索引的死锁(会话交叉插入相同的主键值),②外键未加索引,③表上的位图索引遭到并发更新,④常见事务引发的死锁(例如,两个表之间不同顺序相互更新操作引起的死锁;同一张表删除和更新之间引起的死锁),⑤自治事务引发的死锁。块级死锁主要指的是ITL(Interested Transaction List)死锁。
死锁分类图如下所示:
图2-3 死锁的分类图
2.1 行级死锁
行级锁的发生如下图所示,在A时间,TRANSACRION1和TRANSCTION2分别锁住了它们要UPDATE的一行数据,没有任何问题。但每个TRANSACTION都没有终止。接下来在B时间,它们又试图UPDATE当前正被对方TRANSACTION锁住的行,因此双方都无法获得资源,此时就出现了死锁。之所以称之为死锁,是因为无论每个TRANSACTION等待多久,这种锁都不会被释放。
行级锁的死锁一般是由于应用逻辑设计的问题造成的,其解决方法是通过分析trace文件定位出造成死锁的SQL语句、被互相锁住资源的对象及其记录等信息,提供给应用开发人员进行分析,并修改特定或一系列表的更新(UPDATE)顺序。
以下模拟各种行级死锁的产生过程,版本都是11.2.0.4。
2.1.1 主键、唯一索引的死锁(会话交叉插入相同的主键值)
主键的死锁其本质是唯一索引引起的死锁,这个很容易模拟出来的,新建一张表,设置主键(或创建唯一索引)后插入一个值,然后不要COMMIT,另一个会话插入另一个值,也不要COMMIT,然后再把这两个插入的值互相交换一下,在两个会话中分别插入,死锁就会产生。
会话1,sid为156:
SYS@lhrdb> set sqlprompt "_user'@'_connect_identifier S1> " SYS@lhrdb S1> DROP TABLE T_DEADLOCK_PRIMARY_LHR;
Table dropped.
====>>>>> CREATE UNIQUE INDEX IDX_TEST_LHR ON T_DEADLOCK_PRIMARY_LHR(ID); SYS@lhrdb S1> CREATE TABLE T_DEADLOCK_PRIMARY_LHR(ID NUMBER PRIMARY KEY);
Table created.
SYS@lhrdb S1> select userenv('sid') from dual;
USERENV('SID') -------------- 156
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(1);
1 row created.
SYS@lhrdb S1> |
会话2,sid为156:
SYS@lhrdb> set sqlprompt "_user'@'_connect_identifier S2> " SYS@lhrdb S2> select userenv('sid') from dual;
USERENV('SID') -------------- 191
SYS@lhrdb S2> INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(2);
1 row created.
SYS@lhrdb S2> INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(1);
====>>>>> 产生了阻塞 |
SELECT NVL(A.SQL_ID, A.PREV_SQL_ID) SQL_ID,
A.BLOCKING_SESSION,
A.SID,
A.SERIAL#,
A.LOGON_TIME,
A.EVENT
FROM GV$SESSION A
WHERE A.SID IN (156,191)
ORDER BY A.LOGON_TIME;
156阻塞了191会话,即会话1阻塞了会话2。
会话1再次插入数据:
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(2);
====>>>>> 产生了阻塞 |
此时,去会话2看的时候,已经报出了死锁的错误:
此时的阻塞已经发生了变化:
告警日志:
Fri Sep 23 09:03:11 2016 ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/lhrdb/lhrdb/trace/lhrdb_ora_25886852.trc. |
其内容可以看到很经典的一段:
Deadlock graph: ---------Blocker(s)-------- ---------Waiter(s)--------- Resource Name process session holds waits process session holds waits TX-0008000c-000008dc 38 191 X 29 156 S TX-00030016-00000892 29 156 X 38 191 S
session 191: DID 0001-0026-00000115 session 156: DID 0001-001D-0000000D session 156: DID 0001-001D-0000000D session 191: DID 0001-0026-00000115
Rows waited on: Session 191: no row Session 156: no row |
这就是主键的死锁,模拟完毕。
此时,若是会话2执行提交后,会话1就会报错,违反唯一约束:
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(2);
INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(2) * ERROR at line 1: ORA-00001: unique constraint (SYS.SYS_C0011517) violated |
会话1: DROP TABLE T_DEADLOCK_PRIMARY_LHR; CREATE TABLE T_DEADLOCK_PRIMARY_LHR(ID NUMBER PRIMARY KEY); --CREATE UNIQUE INDEX IDX_TEST_LHR ON T_DEADLOCK_PRIMARY_LHR(ID); select userenv('sid') from dual; INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(1);
会话2: select userenv('sid') from dual; INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(2); INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(1);
会话1:---死锁产生 INSERT INTO T_DEADLOCK_PRIMARY_LHR VALUES(2);
SELECT NVL(A.SQL_ID, A.PREV_SQL_ID) SQL_ID, A.BLOCKING_SESSION, A.SID, A.SERIAL#, A.LOGON_TIME, A.EVENT FROM GV$SESSION A WHERE A.SID IN (156,191) ORDER BY A.LOGON_TIME; |
2.1.2 外键的死锁(外键未加索引)
外键未加索引很容易导致死锁。在以下两种情况下,Oracle在修改父表后会对子表加一个全表锁:
1. 如果更新了父表的主键,由于外键上没有索引,所以子表会被锁住。
2. 如果删除了父表中的一行,由于外键上没有索引,整个子表也会被锁住。
总之,就是更新或者删除父表的主键,都会导致对其子表加一个全表锁。
如果父表存在删除记录或者更改外键列的情形,那么就需要在子表上为外键列创建索引。
除了全表锁外,在以下情况下,未加索引的外键也可能带来问题:
1. 如果有ON DELETE CASCADE,而且没有对子表加索引:例如,EMP是DEPT的子表,DELETE DEPTNO = 10应该CASCADE(级联)至EMP[4]。如果EMP中的DEPTNO没有索引,那么删除DEPT表中的每一行时都会对EMP做一个全表扫描。这个全表扫描可能是不必要的,而且如果从父表删除多行,父表中每删除一行就要扫描一次子表。
2. 从父表查询子表:再次考虑EMP/DEPT例子。利用DEPTNO查询EMP表是相当常见的。如果频繁地运行以下查询(例如,生成一个报告),你会发现没有索引会使查询速度变慢:
SELECT * FROM DEPT, EMP
WHERE EMP.DEPTNO = DEPT.DEPTNO
AND DEPT.DEPTNO = :X;
那么,什么时候不需要对外键加索引呢?答案是,一般来说,当满足以下条件时不需要加索引:
1. 没有从父表删除行。
2. 没有更新父表的惟一键/主键值。
3. 没有从父表联结子表。
如果满足上述全部3个条件,那你完全可以跳过索引,不需要对外键加索引。不过个人还是强烈建议对子表添加索引,既然已经创建了外键,就不在乎再多一个索引吧,因为一个索引所增加的代价比如死锁,与缺失这个索引所带来的问题相比,是微不足道的。
子表上为外键列建立索引,可以:
1)提高针对外键列的查询或改动性能
2)减小表级锁粒度,降低死锁发生的可能性
外键的死锁可以这样通俗的理解:有两个表A和B:A是父表,B是子表。如果没有在B表中的外键加上索引,那么A表在更新或者删除主键时,都会在表B上加一个全表锁。这是为什么呢?因为我们没有给外键加索引,在更新或者删除A表主键的时候,需要查看子表B中是否有对应的记录,以判断是否可以更新删除。那如何查找呢?当然只能在子表B中一条一条找了,因为我们没有加索引吗。既然要在子表B中一条一条地找,那就得把整个子表B都锁定了。由此就会导致以上一系列问题。
实验过程:
会话1首先建立子表和父表
SYS@lhrdb S1> CREATE TABLE T_DEADLOCK_P_LHR (ID NUMBER PRIMARY KEY, NAME VARCHAR2(30)); --父表的主键
Table created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_P_LHR VALUES (1, 'A');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_P_LHR VALUES (2, 'B');
1 row created.
SYS@lhrdb S1> CREATE TABLE T_DEADLOCK_F_LHR (FID NUMBER, FNAME VARCHAR2(30), FOREIGN KEY (FID) REFERENCES T_DEADLOCK_P_LHR); --子表的外键
Table created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_F_LHR VALUES (1, 'C');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_F_LHR VALUES (2, 'D');
1 row created.
SYS@lhrdb S1> SYS@lhrdb S1> COMMIT;
Commit complete.
|
会话1执行一个删除操作,这时候在子表和父表上都加了一个Row-X(SX)锁
SYS@lhrdb S1> delete from T_DEADLOCK_F_LHR where FID=1;
1 row deleted.
SYS@lhrdb S1> delete from T_DEADLOCK_P_LHR where id=1;
1 row deleted. |
查询会话1的锁信息:
SELECT LK.SID,
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,
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(+)
AND SE.SID IN (156,191)
ORDER BY SID;
BLOCK为0表示没有阻塞其它的锁。
会话2:执行另一个删除操作,发现这时候第二个删除语句等待
SYS@lhrdb S2> delete from T_DEADLOCK_F_LHR where FID=2;
1 row deleted.
SYS@lhrdb S2> delete from T_DEADLOCK_P_LHR where id=2;
====>>>>> 产生了阻塞 |
BLOCK为1表示阻塞了其它的锁。
会话1执行删除语句,死锁发生
SYS@lhrdb S1> delete from T_DEADLOCK_P_LHR where id=1;
====>>>>> 产生了阻塞,而会话2产生了死锁 |
SYS@lhrdb S2> delete from T_DEADLOCK_P_LHR where id=2;
delete from T_DEADLOCK_P_LHR where id=2 * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource
|
告警日志:
Fri Sep 23 10:31:10 2016 ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/lhrdb/lhrdb/trace/lhrdb_ora_25886852.trc. |
查看内容:
*** 2016-09-23 10:31:10.212 DEADLOCK DETECTED ( ORA-00060 )
[Transaction Deadlock]
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 TM-00017731-00000000 38 191 SX SSX 29 156 SX SSX TM-00017731-00000000 29 156 SX SSX 38 191 SX SSX
session 191: DID 0001-0026-00000115 session 156: DID 0001-001D-0000000D session 156: DID 0001-001D-0000000D session 191: DID 0001-0026-00000115
Rows waited on: Session 191: no row Session 156: no row |
回滚会话建立外键列上的索引:
SYS@lhrdb S1> rollback;
Rollback complete.
SYS@lhrdb S1> create index ind_p_f_fid on T_DEADLOCK_F_LHR(fid);
Index created. |
重复上面的步骤会话1删除子表记录:
---会话1:
SYS@lhrdb S1> delete from T_DEADLOCK_F_LHR where FID=1;
1 row deleted.
SYS@lhrdb S1> delete from T_DEADLOCK_P_LHR where id=1;
1 row deleted.
SYS@lhrdb S1>
|
---会话2:
SYS@lhrdb S2> delete from T_DEADLOCK_F_LHR where FID=2;
1 row deleted.
SYS@lhrdb S2> delete from T_DEADLOCK_P_LHR where id=2;
1 row deleted. |
所有的删除操作都可以成功执行,也没有阻塞的生成,重点就是在外键列上建立索引。
一、 脚本
DROP TABLE T_DEADLOCK_P_LHR; CREATE TABLE T_DEADLOCK_P_LHR (ID NUMBER PRIMARY KEY, NAME VARCHAR2(30)); --父表的主键 INSERT INTO T_DEADLOCK_P_LHR VALUES (1, 'A'); INSERT INTO T_DEADLOCK_P_LHR VALUES (2, 'B');
--子表 DROP TABLE T_DEADLOCK_F_LHR; CREATE TABLE T_DEADLOCK_F_LHR (FID NUMBER, FNAME VARCHAR2(30), FOREIGN KEY (FID) REFERENCES T_DEADLOCK_P_LHR); --子表的外键 INSERT INTO T_DEADLOCK_F_LHR VALUES (1, 'C'); INSERT INTO T_DEADLOCK_F_LHR VALUES (2, 'D');
COMMIT;
---执行一个删除操作,这时候在子表和父表上都加了一个Row-S(SX)锁 delete from T_DEADLOCK_F_LHR where FID=1; delete from T_DEADLOCK_P_LHR where id=1; ---会话2:执行另一个删除操作,发现这时候第二个删除语句等待 delete from T_DEADLOCK_F_LHR where FID=2; delete from T_DEADLOCK_P_LHR where id=2; ---会话1:死锁发生 delete from T_DEADLOCK_P_LHR where id=1;
---回滚会话建立外键列上的索引: create index ind_p_f_fid on T_DEADLOCK_F_LHR(fid);
--重复上面的步骤会话1删除子表记录: ---会话1: delete from T_DEADLOCK_F_LHR where FID=1; delete from T_DEADLOCK_P_LHR where id=1; ---会话2:执行另一个删除操作,发现这时候第二个删除语句等待 delete from T_DEADLOCK_F_LHR where FID=2; delete from T_DEADLOCK_P_LHR where id=2; |
2.1.3 位图(BITMAP)索引死锁
表上的位图索引遭到并发更新也很容易产生死锁。在有位图索引存在的表上面,其实很容易就引发阻塞与死锁。这个阻塞不是发生在表上面,而是发生在索引上。因为位图索引锁定的范围远远比普通的b-tree索引锁定的范围大。
SYS@lhrdb S1> CREATE TABLE T_DEADLOCK_BITMAP_LHR (ID NUMBER , NAME VARCHAR2(10));
Table created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (1,'A');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (1,'B');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (1,'C');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (2,'A');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (2,'B');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (2,'C');
1 row created.
SYS@lhrdb S1> COMMIT;
Commit complete.
|
--那么在ID列上建bitmap index的话,所有ID=1的会放到一个位图中,所有ID=2的是另外一个位图,而在执行DML操作的时候,锁定的将是整个位图中的所有行,而不仅仅是DML涉及到的行。由于锁定的粒度变粗,bitmap index更容易导致死锁的发生。
会话1:此时所有ID=1的行都被锁定
SYS@lhrdb S1> CREATE BITMAP INDEX IDX_DEADLOCK_BITMAP_LHR ON T_DEADLOCK_BITMAP_LHR(ID);
Index created.
SYS@lhrdb S1> UPDATE T_DEADLOCK_BITMAP_LHR SET ID=3 WHERE ID=1 AND NAME='A';
1 row updated.
|
会话2:此时所有ID=2的行都被锁定
SYS@lhrdb S2> UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='A';
1 row updated.
|
会话1:此时会话被阻塞
SYS@lhrdb S1> UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='B';
====>>>>> 产生了阻塞 |
会话2:会话被阻塞
SYS@lhrdb S2> UPDATE T_DEADLOCK_BITMAP_LHR SET ID=3 WHERE ID=1 AND NAME='B';
====>>>>> 产生了阻塞 |
再回到SESSION 1,发现系统检测到了死锁的发生
SYS@lhrdb S1> UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='B'; UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='B' * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource
|
告警日志:
Fri Sep 23 11:20:21 2016 ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/lhrdb/lhrdb/trace/lhrdb_ora_19726460.trc. |
内容:
*** 2016-09-23 11:26:51.264 DEADLOCK DETECTED ( ORA-00060 )
[Transaction Deadlock]
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-0009000e-00000b0f 29 156 X 38 191 S TX-00070001-00000b2c 38 191 X 29 156 S
session 156: DID 0001-001D-0000000D session 191: DID 0001-0026-00000115 session 191: DID 0001-0026-00000115 session 156: DID 0001-001D-0000000D
Rows waited on: Session 156: obj - rowid = 00017734 - AAAXc0AAAAAAAAAAAA (dictionary objn - 96052, file - 0, block - 0, slot - 0) Session 191: obj - rowid = 00017734 - AAAXc0AAAAAAAAAAAA (dictionary objn - 96052, file - 0, block - 0, slot - 0) |
死锁发生的根本原因是对于资源的排他锁定顺序不一致。上面的试验中,session1对于bitmap index中的2个位图是先锁定ID=1的位图,然后请求ID=2的位图,而在此之前ID=2的位图已经被session2锁定。session2则先锁定ID=2的位图,然后请求ID=2的位图,而此前ID=1的位图已经被session1锁定。于是,session1等待session2释放ID=2的位图上的锁,session2等待session1释放ID=1的位图上的锁,死锁就发生了
而如果我们创建的是普通的B*Tree index,重复上面的试验则不会出现任何的阻塞和死锁,这是因为锁定的只是DML操作涉及到的行,而不是所有ID相同的行。
一、 脚本CREATE TABLE T_DEADLOCK_BITMAP_LHR (ID NUMBER , NAME VARCHAR2(10)); INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (1,'A'); INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (1,'B'); INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (1,'C'); INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (2,'A'); INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (2,'B'); INSERT INTO T_DEADLOCK_BITMAP_LHR VALUES (2,'C'); COMMIT;
CREATE BITMAP INDEX IDX_DEADLOCK_BITMAP_LHR ON T_DEADLOCK_BITMAP_LHR(ID);
--会话1:此时所有ID=1的行都被锁定 UPDATE T_DEADLOCK_BITMAP_LHR SET ID=3 WHERE ID=1 AND NAME='A'; --会话2:此时所有ID=2的行都被锁定 UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='A';
--会话1:此时会话被阻塞 UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='B';
--会话2:会话被阻塞,再回到SESSION 1,发现系统检测到了死锁的发生 UPDATE T_DEADLOCK_BITMAP_LHR SET ID=3 WHERE ID=1 AND NAME='B';
--会话1: UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='B'; UPDATE T_DEADLOCK_BITMAP_LHR SET ID=4 WHERE ID=2 AND NAME='B' * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource |
2.1.4 常见事务引发的死锁
如果你有两个会话,每个会话都持有另一个会话想要的资源,此时就会出现死锁(deadlock)。例如,如果我的数据库中有两个表A和B,每个表中都只有一行,就可以很容易地展示什么是死锁。我要做的只是打开两个会话(例如,两个SQL*Plus会话)。在会话A中更新表A,并在会话B中更新表B。现在,如果我想在会话B中更新表A,就会阻塞。会话A已经锁定了这一行。这不是死锁;只是阻塞而已。因为会话A还有机会提交或回滚,这样会话B就能继续。如果我再回到会话A,试图更新表B,这就会导致一个死锁。要在这两个会话中选择一个作为“牺牲品”,让它的语句回滚。
想要更新表B的会话A还阻塞着,Oracle不会回滚整个事务。只会回滚与死锁有关的某条语句。会话B仍然锁定着表B中的行,而会话A还在耐心地等待这一行可用。收到死锁消息后,会话B必须决定将表B上未执行的工作提交还是回滚,或者继续走另一条路,以后再提交。一旦这个会话执行提交或回滚,另一个阻塞的会话就会继续,好像什么也没有发生过一样。
一、 模拟一:两个表之间不同顺序相互更新操作引起的死锁1、创建两个简单的表A和B,每个表中仅仅包含一个字段id。
[ZFZHLHRDB2:oracle]:/oracle>ORACLE_SID=raclhr2 [ZFZHLHRDB2:oracle]:/oracle>sqlplus / as sysdba
SQL*Plus: Release 11.2.0.4.0 Production on Mon Jun 20 09:40:24 2016
Copyright (c) 1982, 2013, Oracle. All rights reserved.
Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production With the Partitioning, Real Application Clusters, Automatic Storage Management, OLAP, Data Mining and Real Application Testing options
SYS@raclhr2> select * from v$version;
BANNER -------------------------------------------------------------------------------- Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production PL/SQL Release 11.2.0.4.0 - Production CORE 11.2.0.4.0 Production TNS for IBM/AIX RISC System/6000: Version 11.2.0.4.0 - Production NLSRTL Version 11.2.0.4.0 - Production
SYS@raclhr2> show parameter cluster
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ cluster_database boolean TRUE cluster_database_instances integer 2 cluster_interconnects string
SYS@raclhr2> create table A (id int);
Table created.
SYS@raclhr2> create table B (id int);
Table created.
|
2、每张表中仅初始化一条数据
SYS@raclhr2> insert into A values (1);
1 row created.
SYS@raclhr2> insert into B values (2);
1 row created.
SYS@raclhr2> commit;
Commit complete. |
3、在第一个会话session1中更新表A中的记录“1”为“10000”,不提交;在第二个会话session2中更新表B中的记录“2”为“20000”,不提交
session1的情况:
SYS@raclhr2> insert into A values (1);
1 row created.
SYS@raclhr2> insert into B values (2);
1 row created.
SYS@raclhr2> commit;
Commit complete. |
SYS@raclhr2> SELECT a.SID, 2 b.SERIAL# , 3 c.SPID 4 FROM v$mystat a, 5 v$session b , 6 v$process c 7 WHERE a.SID = b.SID 8 and b.PADDR=c.ADDR 9 AND rownum = 1;
SID SERIAL# SPID ---------- ---------- ------------------------ 133 3 20906088
SYS@raclhr2> update A set id = 10000 where id = 1;
1 row updated.
|
session2的情况:
SYS@raclhr2> SELECT a.SID, 2 b.SERIAL# , 3 c.SPID 4 FROM v$mystat a, 5 v$session b , 6 v$process c 7 WHERE a.SID = b.SID 8 and b.PADDR=c.ADDR 9 AND rownum = 1;
SID SERIAL# SPID ---------- ---------- ------------------------ 195 21 11010172
SYS@raclhr2> update B set id = 20000 where id = 2;
1 row updated.
SYS@raclhr2> |
4、此时,没有任何问题发生。OK,现在注意一下下面的现象,我们再回到会话session1中,更新表B的记录,此时出现了会话阻塞,更新hang住不能继续。
SYS@raclhr2> update B set id = 10000 where id = 2; |
这里出现了“锁等待”(“阻塞”)的现象,原因很简单,因为在session2中已经对这条数据执行过update操作没有提交表示已经对该行加了行级锁。
SYS@raclhr2> set line 9999 SYS@raclhr2> SELECT A.INST_ID, 2 A.SID, 3 A.SERIAL#, 4 A.SQL_ID, 5 A.BLOCKING_INSTANCE, 6 A.BLOCKING_SESSION, 7 A.EVENT 8 FROM gv$session a 9 WHERE a.USERNAME IS NOT NULL 10 and a.STATUS = 'ACTIVE' 11 and a.BLOCKING_SESSION IS NOT NULL ;
INST_ID SID SERIAL# SQL_ID BLOCKING_INSTANCE BLOCKING_SESSION EVENT ---------- ---------- ---------- ------------- ----------------- ---------------- ------------------------------ 2 133 3 6k793mj0duubw 2 195 enq: TX - row lock contention |
我们可以通过v$session视图看到,实例2的195阻塞了实例2的133会话,即本实验中的session2阻塞了session1。
6、接下来再执行一条SQL后死锁就会产生了:在session2中,更新表A的记录
SYS@raclhr2> update A set id = 10000 where id = 1; |
这里还是长时间的等待,但是这里发生了死锁,这个时候我们去第一个会话session1中看一下,原先一直在等待的SQL语句报了如下的错误:
SYS@raclhr2> update B set id = 10000 where id = 2; update B set id = 10000 where id = 2 * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource
|
若此时查询v$session视图可以看到实例2的133阻塞了实例2的195会话,即本实验中的session1阻塞了session2,和刚刚的阻塞情况相反,说明oracle做了自动处理:
SYS@raclhr2> set line 9999 SYS@raclhr2> SELECT A.INST_ID, 2 A.SID, 3 A.SERIAL#, 4 A.SQL_ID, 5 A.BLOCKING_INSTANCE, 6 A.BLOCKING_SESSION, 7 A.EVENT 8 FROM gv$session a 9 WHERE a.USERNAME IS NOT NULL 10 and a.STATUS = 'ACTIVE' 11 and a.BLOCKING_SESSION IS NOT NULL ;
INST_ID SID SERIAL# SQL_ID BLOCKING_INSTANCE BLOCKING_SESSION EVENT ---------- ---------- ---------- ------------- ----------------- ---------------- ------------------------------ 2 195 21 5q7t3877fdu3n 2 133 enq: TX - row lock contention
|
更进一步:查看一下alert警告日志文件发现有如下的记录:
Mon Jun 20 10:10:56 2016 Global Enqueue Services Deadlock detected. More info in file /u01/app/oracle/diag/rdbms/raclhr/raclhr2/trace/raclhr2_lmd0_19923170.trc. |
若是单机环境,报警日志为:
ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/dlhr/dlhr/trace/dlhr_ora_16973880.trc. |
可以看到该文件是由lmd进程生成的,为rac的特有进程,完成CacheFusion的作用,再进一步:看看系统自动生成的trace文件中记录了什么:
user session for deadlock lock 0x70001001569c378 sid: 133 ser: 3 audsid: 4294967295 user: 0/SYS flags: (0x41) USR/- flags_idl: (0x1) BSY/-/-/-/-/- flags2: (0x40009) -/-/INC pid: 60 O/S info: user: oracle, term: UNKNOWN, ospid: 20906088 image: oracle@ZFZHLHRDB2 (TNS V1-V3) client details: O/S info: user: oracle, term: pts/0, ospid: 16122014 machine: ZFZHLHRDB2 program: sqlplus@ZFZHLHRDB2 (TNS V1-V3) application name: sqlplus@ZFZHLHRDB2 (TNS V1-V3), hash value=346557658 current SQL: update B set id = 10000 where id = 2 DUMP LOCAL BLOCKER: initiate state dump for DEADLOCK possible owner[60.20906088] on resource TX-00140013-0000072D
user session for deadlock lock 0x700010015138660 sid: 195 ser: 21 audsid: 4294967295 user: 0/SYS flags: (0x41) USR/- flags_idl: (0x1) BSY/-/-/-/-/- flags2: (0x40009) -/-/INC pid: 46 O/S info: user: oracle, term: UNKNOWN, ospid: 11010172 image: oracle@ZFZHLHRDB2 (TNS V1-V3) client details: O/S info: user: oracle, term: pts/1, ospid: 16646154 machine: ZFZHLHRDB2 program: sqlplus@ZFZHLHRDB2 (TNS V1-V3) application name: sqlplus@ZFZHLHRDB2 (TNS V1-V3), hash value=346557658 current SQL: update A set id = 10000 where id = 1 DUMP LOCAL BLOCKER: initiate state dump for DEADLOCK possible owner[46.11010172] on resource TX-000B0018-00000416
|
若是单机环境比较明显:
[Transaction Deadlock]
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-000d0005-00000047 41 37 X 25 34 X TX-0008000a-0000036b 25 34 X 41 37 X
session 37: DID 0001-0029-00000003session 34: DID 0001-0019-0000000D session 34: DID 0001-0019-0000000Dsession 37: DID 0001-0029-00000003
Rows waited on: Session 37: obj - rowid = 00015FE7 - AAAV/nAABAAAXeBAAA (dictionary objn - 90087, file - 1, block - 96129, slot - 0) Session 34: obj - rowid = 00015FE6 - AAAV/mAABAAAXZ5AAA (dictionary objn - 90086, file - 1, block - 95865, slot - 0) |
注意trace文件中的一行如下提示信息,说明一般情况下都是应用和人为的,和Oracle同学没有关系:
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.
造成死锁的原因就是多个线程或进程对同一个资源的争抢或相互依赖。这里列举一个对同一个资源的争抢造成死锁的实例。
SYS@lhrdb S1> CREATE TABLE T_DEADLOCK_DU_LHR ( ID NUMBER, test VARCHAR(10) ) ;
Table created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_DU_LHR VALUES(1,'test1');
1 row created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_DU_LHR VALUES(2,'test2');
1 row created.
SYS@lhrdb S1> COMMIT;
Commit complete.
SYS@lhrdb S1> SELECT * FROM T_DEADLOCK_DU_LHR;
ID TEST ---------- ---------- 1 test1 2 test2 |
会话1更新第一条记录:
SYS@lhrdb S1> UPDATE T_DEADLOCK_DU_LHR T SET T.TEST=100 WHERE ID=1;
1 row updated.
|
会话2删除第二条记录:
SYS@lhrdb S2> DELETE FROM T_DEADLOCK_DU_LHR T WHERE ID=2;
1 row deleted.
|
接下来会话1更新第二条记录,这是就产生了阻塞:
SYS@lhrdb S1> UPDATE T_DEADLOCK_DU_LHR T SET T.TEST=100 WHERE ID=2;
====>>>>> 产生了阻塞,会话2阻塞了会话1
|
会话2删除第一条记录:
SYS@lhrdb S2> DELETE FROM T_DEADLOCK_DU_LHR T WHERE ID=1;
====>>>>> 产生了阻塞,此时会话1产生死锁 |
查看会话1:
SYS@lhrdb S1> UPDATE T_DEADLOCK_DU_LHR T SET T.TEST=100 WHERE ID=2;
UPDATE T_DEADLOCK_DU_LHR T SET T.TEST=100 WHERE ID=2 * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource
SYS@lhrdb S1> SYS@lhrdb S1> SYS@lhrdb S1> SYS@lhrdb S1> SYS@lhrdb S1> SYS@lhrdb S1> |
告警日志:
Fri Sep 23 15:10:55 2016 ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/lhrdb/lhrdb/trace/lhrdb_ora_19726460.trc. |
内容:
*** 2016-09-23 15:10:55.326 DEADLOCK DETECTED ( ORA-00060 )
[Transaction Deadlock]
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-00090008-00000b0c 29 156 X 38 191 X TX-000a0007-00000d28 38 191 X 29 156 X
session 156: DID 0001-001D-0000000D session 191: DID 0001-0026-00000115 session 191: DID 0001-0026-00000115 session 156: DID 0001-001D-0000000D
Rows waited on: Session 156: obj - rowid = 0001773F - AAAXc/AABAABc2RAAB (dictionary objn - 96063, file - 1, block - 380305, slot - 1) Session 191: obj - rowid = 0001773F - AAAXc/AABAABc2RAAA (dictionary objn - 96063, file - 1, block - 380305, slot - 0) |
2.1.5 自治事务引发的死锁
一般来说构成死锁至少需要两个会话,而自治事务是一个会话可能引发死锁。
自治事务死锁情景:存储过程INSERT表A,然后INSERT表B;其中INSERT表A触发TRIGGER T,T也INSERT表B,T是自治事务(AT),AT试图获取对B的锁,结果B已经被主事务所HOLD,这里会报出来ORA-00060 – 等待资源时检查到死锁.
解决方法:去掉了T中的PRAGMA AUTONOMOUS_TRANSACTION声明,保持和存储过程事务一致.
一、 模拟一:更新在主事务中如果更新了部分记录,这时若自治事务更新同样的记录,就会造成死锁,下面通过一个简单的例子模拟了这个错误的产生:
SYS@lhrdb S1> CREATE TABLE T_DEADLOCK_AT2_LHR(ID NUMBER, NAME VARCHAR2(30));
Table created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_AT2_LHR SELECT ROWNUM, TNAME FROM TAB WHERE ROWNUM<=4;
4 rows created.
SYS@lhrdb S1> COMMIT;
Commit complete.
SYS@lhrdb S1> CREATE OR REPLACE PROCEDURE PRO_TESTAT_LHR AS 2 PRAGMA AUTONOMOUS_TRANSACTION; 3 BEGIN 4 UPDATE T_DEADLOCK_AT2_LHR SET NAME = NAME WHERE ID = 1; 5 COMMIT; 6 END; 7 /
Procedure created.
SYS@lhrdb S1> UPDATE T_DEADLOCK_AT2_LHR SET NAME = NAME WHERE ID = 1;
1 row updated.
SYS@lhrdb S1> EXEC PRO_TESTAT_LHR;
BEGIN PRO_TESTAT_LHR; END;
* ERROR at line 1: ORA-00060: deadlock detected while waiting for resource ORA-06512: at "SYS.PRO_TESTAT_LHR", line 4 ORA-06512: at line 1
SYS@lhrdb S1> SYS@lhrdb S1> |
在使用自治事务的时候要避免当前事务锁定的记录和自治事务中锁定的记录相互冲突。
告警日志:
Fri Sep 23 14:03:10 2016 ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/lhrdb/lhrdb/trace/lhrdb_ora_19726460.trc. |
内容:
*** 2016-09-23 14:10:34.974 DEADLOCK DETECTED ( ORA-00060 )
[Transaction Deadlock]
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-000a0015-00000d25 38 191 X 38 191 X
session 191: DID 0001-0026-00000115 session 191: DID 0001-0026-00000115
Rows waited on: Session 191: obj - rowid = 0001773A - AAAXc6AABAABc2BAAA (dictionary objn - 96058, file - 1, block - 380289, slot - 0)
----- Information for the OTHER waiting sessions ----- ----- End of information for the OTHER waiting sessions -----
Information for THIS session:
----- Current SQL Statement for this session (sql_id=3w3thujdh1y3a) ----- UPDATE T_DEADLOCK_AT2_LHR SET NAME = NAME WHERE ID = 1 ----- PL/SQL Stack ----- ----- PL/SQL Call Stack ----- object line object handle number name 700010052c9cf40 4 procedure SYS.PRO_TESTAT_LHR 700010052cb7588 1 anonymous block |
CREATE TABLE T_DEADLOCK_AT2_LHR(ID NUMBER, NAME VARCHAR2(30)); INSERT INTO T_DEADLOCK_AT2_LHR SELECT ROWNUM, TNAME FROM TAB WHERE ROWNUM<=4; COMMIT;
CREATE OR REPLACE PROCEDURE PRO_TESTAT_LHR AS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN UPDATE T_DEADLOCK_AT2_LHR SET NAME = NAME WHERE ID = 1; COMMIT; END; /
UPDATE T_DEADLOCK_AT2_LHR SET NAME = NAME WHERE ID = 1; exec PRO_TESTAT_LHR;
ERROR AT LINE 1: ORA-00060: DEADLOCK DETECTED WHILE WAITING FOR RESOURCE ORA-06512: AT "SYS.PRO_TESTAT_LHR", LINE 4 ORA-06512: AT LINE 3 |
主事务和自治事务插入的是同一个主键值也会引起死锁。
SYS@lhrdb S1> CREATE TABLE T_DEADLOCK_AT_LHR(X INT PRIMARY KEY,Y INT);
Table created.
SYS@lhrdb S1> CREATE OR REPLACE PROCEDURE PRO_AUTO_PROC_LHR(P_X INT,P_Y INT) AS 2 PRAGMA AUTONOMOUS_TRANSACTION; 3 BEGIN 4 INSERT INTO T_DEADLOCK_AT_LHR(X,Y) VALUES(P_X,P_Y); 5 COMMIT; 6 END; 7 /
Procedure created.
SYS@lhrdb S1> INSERT INTO T_DEADLOCK_AT_LHR(X,Y) VALUES(1,1);
1 row created.
SYS@lhrdb S1> EXEC PRO_AUTO_PROC_LHR(1,2); BEGIN PRO_AUTO_PROC_LHR(1,2); END;
* ERROR at line 1: ORA-00060: deadlock detected while waiting for resource ORA-06512: at "SYS.PRO_AUTO_PROC_LHR", line 4 ORA-06512: at line 1 |
告警日志:
Fri Sep 23 13:49:06 2016 ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/lhrdb/lhrdb/trace/lhrdb_ora_19726460.trc. |
内容:
*** 2016-09-23 13:49:06.546 DEADLOCK DETECTED ( ORA-00060 )
[Transaction Deadlock]
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-000a000b-00000d28 29 156 X 29 156 S
session 156: DID 0001-001D-0000000D session 156: DID 0001-001D-0000000D
Rows waited on: Session 156: no row
----- Information for the OTHER waiting sessions ----- ----- End of information for the OTHER waiting sessions -----
Information for THIS session:
----- Current SQL Statement for this session (sql_id=4fv0tmjrzv28u) ----- INSERT INTO T_DEADLOCK_AT_LHR(X,Y) VALUES(:B2 ,:B1 ) ----- PL/SQL Stack ----- ----- PL/SQL Call Stack ----- object line object handle number name 70001005a9a0580 4 procedure SYS.PRO_AUTO_PROC_LHR 700010052d18b90 1 anonymous block |
可以看到,等待的和持有锁的是同一个会话,根据trace信息记录的对象,发现问题是自治事务导致的。
1、 脚本CREATE TABLE T_DEADLOCK_AT_LHR(X INT PRIMARY KEY,Y INT);
CREATE OR REPLACE PROCEDURE PRO_AUTO_PROC_LHR(P_X INT,P_Y INT) AS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO T_DEADLOCK_AT_LHR(X,Y) VALUES(P_X,P_Y); COMMIT; END; /
INSERT INTO T_DEADLOCK_AT_LHR(X,Y) VALUES(1,1);
SQL> EXEC PRO_AUTO_PROC_LHR(1,2); BEGIN AUTO_PROC(1,2); END;
* ERROR AT LINE 1: ORA-00060: DEADLOCK DETECTED WHILE WAITING FOR RESOURCE ORA-06512: AT "BOCNET.AUTO_PROC", LINE 4 ORA-06512: AT LINE 1 |
2.2 块级死锁
块级死锁其实指的就是ITL死锁。
2.2.1 ITL简介
ITL(Interested Transaction List)是Oracle数据块内部的一个组成部分,用来记录该块所有发生的事务,有的时候也叫ITL槽位。如果一个事务一直没有提交,那么,这个事务将一直占用一个ITL槽位,ITL里面记录了事务信息、回滚段的入口和事务类型等等。如果这个事务已经提交,那么,ITL槽位中还保存有这个事务提交时候的SCN号。ITL的个数受表的存储参数INITRANS控制,在一个块内部,默认分配了2个ITL的个数,如果这个块内还有空闲空间,那么Oracle是可以利用这些空闲空间再分配ITL。如果没有了空闲空间,那么,这个块因为不能分配新的ITL,所以,就可能发生ITL等待。如果在并发量特别大的系统中,那么最好分配足够的ITL个数,或者设置足够的PCTFREE,保证ITL能扩展,但是PCTFREE有可能是被行数据给消耗掉的,例如UPDATE,所以,也有可能导致块内部的空间不够而导致ITL等待,出现了ITL等待就可能导致ITL死锁。
一、 ITL结构如果DUMP一个块(命令:alter system dump datafile X block XXX;),那么在DUMP文件中就可以看到ITL信息:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0008.002.000009e9 0x00c0108b.04ac.24 --U- 3 fsc 0x0000.00752951 0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000 |
1) Itl: ITL事务槽编号,ITL事务槽号的流水编号
2) Xid:代表对应的事务id(transac[X]tion identified),在回滚段事务表中有一条记录和这个事务对应。Xid由三列使用十六进制编码的数字列表示,分别是:Undo Segment Number +Transaction Table Slot Number+ Wrap,即由undo段号+undo槽号+undo槽号的覆盖次数三部分组成,即usn.slot.sqn,这里0x0008.002.000009e9转换为10进制为8.2.2537,从下边的查询出的结果是相对应的。
SYS@lhrdb> select xidusn,xidslot,xidsqn,ubafil,ubablk,ubasqn,ubarec from v$transaction; XIDUSN XIDSLOT XIDSQN UBAFIL UBABLK UBASQN UBAREC ---------- ---------- ---------- ---------- ---------- ---------- ---------- 8 2 2537 3 4235 1196 36 |
3) Uba:(Undo Block Address),该事务对应的回滚段地址,记录了最近一次的该记录的前镜像(修改前的值)。Uba组成:Undo块地址(undo文件号和数据块号)+回滚序列号+回滚记录号。多版本一致读是Oracle保证读操作不会被事务阻塞的重要特性。当Server Process需要查询一个正在被事务修改,但是尚未提交的数据时,就根据ITL上的uba定位到对应Undo前镜像数据位置。这里的Uba为:0x00c0108b.04ac.24,其中00c0108b(16进制)=0000 0000 1100 0000 0001 0000 1000 1011(2进制,共32位,前10位代表文件号,后22位代表数据块号)=文件号为3,块号为4235:(10进制);04ac(16进制)=1196(10进制);24(16进制)=36(10进制)。这个结果和v$transaction查询出来的结果一致。
SELECT UBAFIL 回滚段文件号,UBABLK 数据块号,UBASQN 回滚序列号,UBAREC 回滚记录号 FROM v$transaction ; --查看UBA |
4) Flag:事务标志位,即当前事务槽的状态信息。这个标志位就记录了这个事务的操作状态,各个标志的含义分别是:
标识 |
简介 |
---- |
事务是活动的,未提交,或者在块清除前提交事务 |
C--- |
transaction has been committed and locks cleaned out --事务已经提交,锁已经被清除(提交) |
-B-- |
this undo record contains the undo for this ITL entry |
--U- |
transaction committed (maybe long ago); SCN is an upper bound --事务已经提交,但是锁还没有清除(快速提交) |
---T |
transaction was still active at block cleanout SCN --块清除的SCN被记录时,该事务仍然是活动的,块上如果有已经提交的事务,那么在clean ount的时候,块会被进行清除,但是这个块里面的事务不会被清除。 |
C-U- |
块被延迟清除,回滚段的信息已经改写,SCN显示为最小的SCN,需要由由回滚段重新生成,例如在提交以前,数据块已经flush到数据文件上。 |
5) Lck:表示这个事务所影响的行数,锁住了几行数据,对应有几个行锁。我们看到01号事物槽Lck为3,因为该事物槽中的事物Flag为U,证明该事物已经提交,但是锁还没有清除。再比如对于下边这个ITL:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x0006.020.00000271 0x00800205.0257.13 C--- 0 scn 0x0000.001732c4 0x02 0x0008.006.00000279 0x00800351.0278.15 ---- 1 fsc 0x0000.00000000 |
我们看到01号事物槽Lck为0,因为该事物槽中的事物Flag为C,证明该事物已经提交,锁也被清楚掉了,该事物槽可以被重用了。02号事物槽Lck为1,是因为我对第一行做了一个更新,并且没有提交,Flag为“----”说明该事物是活动的。
6) Scn/Fsc:Commit SCN或者快速提交(Fast Commit Fsc)的SCN。 Scn=SCN of commited TX; Fsc=Free space credit(bytes)每条记录中的行级锁对应Itl条目lb,对应于Itl列表中的序号,即那个事务在该记录上产生的锁。一个事物只有在提交之后才会在ITL事物槽中记录SCN。
二、 ITL个数ITL的个数,受参数INITRANS控制,最大ITL个数受MAXTRANS控制(11g已废弃MAXTRANS),在一个块内部,默认分配了2个ITL的个数。ITL是block级的概念,一个ITL占用块46B的空间,参数INITRANS意味着块中除去block header外一部分存储空间无法被记录使用(46B*INITRANS),当块中还有一定的FREE SPACE时,ORACLE可以使用FREE SPACE构建ITL供事务使用,如果这个块内还有空闲空间,那么Oracle是可以利用这些空闲空间并再分配ITL。如果没有了空闲空间(free space),那么,这个块因为不能分配新的ITL,所以就可能发生ITL等待,即enq: TX - allocate ITL entry等待事件。注意:10g以后MAXTRANS参数被废弃,默认最大支持255个并发。
如果在并发量特别大的系统中,最好分配足够的ITL个数,其实它并浪费不了太多的空间,或者,设置足够的PCTFREE,保证ITL能扩展,但是PCTFREE有可能是被行数据给消耗掉的,如UPDATE,所以,也有可能导致块内部的空间不够而导致ITL等待。
对于表(数据块)来说,INITRANS这个参数的默认值是1。对于索引(索引块)来说,这个参数默认值是2。
ITL等待表现出的等待事件为“TX - allocate ITL entry”,根据MOS(Troubleshooting waits for 'enq: TX - allocate ITL entry' (Doc ID 1472175.1)提供的解决办法,需要修改一些参数,SQL如下,这里假设用户名为TLHR,表名为TLHRBOKBAL,表上的索引名为PK_TLHRBOKBAL:
ALTER TABLE TLHR.TLHRBOKBAL PCTFREE 20 INITRANS 16; ALTER TABLE TLHR.TLHRBOKBAL MOVE NOLOGGING PARALLEL 12; ALTER TABLE TLHR.TLHRBOKBAL LOGGING NOPARALLEL; ALTER INDEX TLHR.PK_TLHRBOKBAL REBUILD PCTFREE 20 INITRANS 16 NOLOGGING PARALLEL 12; ALTER INDEX TLHR.PK_TLHRBOKBAL LOGGING NOPARALLEL; |
无MOS权限的朋友可以去http://blog.itpub.net/26736162/viewspace-2124531/阅读。
2.2.2 ITL引起的死锁案例处理
由ITL不足引发的块级死锁的一个处理案例可以参考我的BLOG:http://blog.itpub.net/26736162/viewspace-2124771/、http://blog.itpub.net/26736162/viewspace-2124735/。
2.2.3 ITL死锁的模拟
我们首先创建一张表T_ITL_LHR,这里指定PCTFREE为0,INITRANS为1,就是为了观察到ITL的真实等待情况,然后我们给这些块内插入数据,把块填满,让它不能有空间分配。
SYS@lhrdb21> SELECT * FROM V$VERSION;
BANNER -------------------------------------------------------------------------------- Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production PL/SQL Release 11.2.0.4.0 - Production CORE 11.2.0.4.0 Production TNS for IBM/AIX RISC System/6000: Version 11.2.0.4.0 - Production NLSRTL Version 11.2.0.4.0 - Production
SYS@lhrdb21> SHOW PARAMETER CLUSTER
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ cluster_database boolean TRUE cluster_database_instances integer 2 cluster_interconnects string
SYS@lhrdb21> CREATE TABLE T_ITL_LHR(A INT) PCTFREE 0 INITRANS 1;
Table created.
SYS@lhrdb21> BEGIN 2 FOR I IN 1 .. 2000 LOOP 3 INSERT INTO T_ITL_LHR VALUES (I); 4 END LOOP; 5 END; 6 /
PL/SQL procedure successfully completed.
SYS@lhrdb21> COMMIT;
Commit complete. |
我们检查数据填充的情况:
SYS@lhrdb21> SELECT F, B, COUNT(*) 2 FROM (SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) F, 3 DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) B 4 FROM T_ITL_LHR) 5 GROUP BY F, B 6 ORDER BY F,B;
F B COUNT(*) ---------- ---------- ---------- 1 94953 734 1 94954 734 1 94955 532 |
可以发现,这2000条数据分布在3个块内部,其中有2个块(94953和94954)填满了,一个块(94955)是半满的。因为有2个ITL槽位,我们需要拿2个满的数据块,4个进程来模拟ITL死锁:
实验步骤 |
会话 |
SID |
要更新的块号 |
要更新的行号 |
是否有阻塞 |
步骤一 |
1 |
19 |
94953 94953 |
1 |
N |
2 |
79 |
2 |
N |
||
3 |
78 |
94954 94954 |
1 |
N |
|
4 |
139 |
2 |
N |
会话1:
SYS@lhrdb21> SELECT USERENV('SID') FROM DUAL;
USERENV('SID') -------------- 19
SYS@lhrdb21> UPDATE T_ITL_LHR SET A=A 2 WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94953 3 AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=1;
1 row updated. |
会话2:
SYS@lhrdb21> SELECT USERENV('SID') FROM DUAL;
USERENV('SID') -------------- 79
SYS@lhrdb21> UPDATE T_ITL_LHR SET A=A 2 WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94953 3 AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=2;
1 row updated.
|
会话3:
SYS@lhrdb21> SELECT USERENV('SID') FROM DUAL;
USERENV('SID') -------------- 78
SYS@lhrdb21> UPDATE T_ITL_LHR SET A=A 2 WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94954 3 AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=1;
1 row updated.
|
会话4:
SYS@lhrdb21> SELECT USERENV('SID') FROM DUAL;
USERENV('SID') -------------- 139
SYS@lhrdb21> UPDATE T_ITL_LHR SET A=A 2 WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94954 3 AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=2;
1 row updated.
|
这个时候系统不存在阻塞,
SELECT NVL(A.SQL_ID, A.PREV_SQL_ID) SQL_ID,
A.BLOCKING_SESSION,
A.SID,
A.SERIAL#,
A.LOGON_TIME,
A.EVENT
FROM GV$SESSION A
WHERE A.SID IN (19, 79,78,139)
ORDER BY A.LOGON_TIME;
以上4个进程把2个不同块的4个ITL槽位给消耗光了,现在的情况,就是让他们互相锁住,达成死锁条件,回到会话1,更新块94954,注意,以上4个操作,包括以下的操作,更新的根本不是同一行数据,主要是为了防止出现的是行锁等待。
实验步骤 |
会话 |
SID |
要更新的块号 |
要更新的行号 |
是否有阻塞 |
步骤一 |
1 |
19 |
94953 94953 |
1 |
N |
2 |
79 |
2 |
N |
||
3 |
78 |
94954 94954 |
1 |
N |
|
4 |
139 |
2 |
N |
||
步骤二 |
1 |
19 |
94954 |
3 |
Y |
3 |
78 |
94953 |
3 |
Y |
会话1:
UPDATE T_ITL_LHR SET A=A
WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94954
AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=3;
会话1出现了等待。
会话3:
UPDATE T_ITL_LHR SET A=A
WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94953
AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=3;
会话3发现出现了等待。
我们查询阻塞的具体情况:
SELECT NVL(A.SQL_ID, A.PREV_SQL_ID) SQL_ID,
A.BLOCKING_SESSION,
A.SID,
A.SERIAL#,
A.LOGON_TIME,
A.EVENT
FROM GV$SESSION A
WHERE A.SID IN (19, 79,78,139)
ORDER BY A.LOGON_TIME;
可以看到,会话1被会话4阻塞了,会话3被会话2阻塞了。
注意,如果是9i,在这里就报死锁了,但是在10g里面,这个时候,死锁是不会发生的,因为这里的会话1还可以等待会话4释放资源,会话3还可以等待会话2释放资源,只要会话2与会话4释放了资源,整个环境又活了,那么我们需要把这两个进程也塞住。
出现的是行锁等待。
实验步骤 |
会话 |
SID |
要更新的块号 |
要更新的行号 |
是否有阻塞 |
步骤一 |
1 |
19 |
94953 94953 |
1 |
N |
2 |
79 |
2 |
N |
||
3 |
78 |
94954 94954 |
1 |
N |
|
4 |
139 |
2 |
N |
||
步骤二 |
1 |
19 |
94954 |
3 |
Y |
3 |
78 |
94953 |
3 |
Y |
|
步骤三 |
2 |
79 |
94954 |
4 |
Y |
4 |
139 |
94953 |
4 |
Y |
会话2,注意,我们也不是更新的同一行数据:
UPDATE T_ITL_LHR SET A=A
WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94954
AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=4;
会话2出现了等待,具体阻塞情况:
我做了几次实验,会话2执行完SQL后,会话3到这里就报出了死锁,但有的时候并没有产生死锁,应该跟系统的阻塞顺序有关,若没有产生死锁,我们可以继续会话4的操作。
SYS@lhrdb21> UPDATE T_ITL_LHR SET A=A 2 WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94953 3 AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=3;
UPDATE T_ITL_LHR SET A=A * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource |
会话4,注意,我们也不是更新的同一行数据:
UPDATE T_ITL_LHR SET A=A
WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94953
AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=4;
会话4发现出现了等待。
虽然,以上的每个更新语句,更新的都不是同一个数据行,但是,的确,所有的进程都被阻塞住了,那么,死锁的条件也达到了,等待一会(这个时间有个隐含参数来控制的:_lm_dd_interval),我们可以看到,会话2出现提示,死锁:
SYS@lhrdb21> UPDATE T_ITL_LHR SET A=A 2 WHERE DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)=94954 3 AND DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)=4;
UPDATE T_ITL_LHR SET A=A * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource |
报出死锁之后的阻塞情况:
我们可以在会话2上继续执行步骤三中的SQL,依然会产生死锁。生成死锁后,在告警日志中有下边的语句:
Fri Sep 09 17:56:55 2016 Global Enqueue Services Deadlock detected. More info in file /oracle/app/oracle/diag/rdbms/lhrdb2/lhrdb21/trace/lhrdb21_lmd0_17039368.trc. |
其中的内容有非常经典的一段Global Wait-For-Graph(WFG):
*** 2016-09-09 17:48:22.216 Submitting asynchronized dump request [1c]. summary=[ges process stack dump (kjdglblkrdm1)]. Global blockers dump end:----------------------------------- Global Wait-For-Graph(WFG) at ddTS[0.395] : BLOCKED 0x700010063d59b90 3 wq 2 cvtops x1001 TX 0x7000b.0xa67(ext 0x2,0x0)[1002-0029-00008387] inst 1 BLOCKER 0x700010063c6d268 3 wq 1 cvtops x28 TX 0x7000b.0xa67(ext 0x2,0x0)[1002-002D-00003742] inst 1 BLOCKED 0x700010063d5adc8 3 wq 2 cvtops x1 TX 0x30021.0x848(ext 0x2,0x0)[1002-002D-00003742] inst 1 BLOCKER 0x700010063d5a4b8 3 wq 1 cvtops x28 TX 0x30021.0x848(ext 0x2,0x0)[1002-0029-00008387] inst 1 |
该实验过程可能有点复杂,小麦苗画了个图来说明整个实验过程:
oracle ora-60 deadlock发生在多个会话的情况
ora-60错误可以发生在两个会话之间,即两个会话之间互相持有一个对方等待的资源(A deadlock occurs when a session (A) wants a resource held by another session (B) , but that session also wants a resource held by the first session (A). )。如果几个会话之间的资源争用是环形,这也是死锁,并且oracle同样会监测到并处理。
以下演示:
DB Version:11.2.0.4
建测试表:
create table scott.tb_60
(
num NUMBER,
txt VARCHAR2(10)
);
insert into scott.tb_60 values ( 1, 'First' );
insert into scott.tb_60 values ( 2, 'Second' );
insert into scott.tb_60 values ( 3, 'Third' );
commit;
会话1:
更新num=1的记录
update scott.tb_60
set txt='a'
where num=1;
会话2:
更新num=2的记录
update scott.tb_60
set txt='b'
where num=2;
更新num=1的记录,此时会话2等待会话1提交或回滚来释放num=1上的tx锁
update scott.tb_60
set txt='a'
where num=1;
会话3:
更新num=3的记录
update scott.tb_60
set txt='c'
where num=3;
更新num=2的记录,此时会话3等待会话2提交或回滚来释放num=2上的tx锁
update scott.tb_60
set txt='b'
where num=2;
会话1:
更新num=3的记录,此时会话1等待会话3提交或回滚来释放num=3上的tx锁,死锁产生
update scott.tb_60
set txt='c'
where num=3;
报错信息:
ORA-00060: deadlock detected while waiting for resource
数据库alert.log中记录错误
ORA-00060: Deadlock detected. More info in file /u01/app/oracle/diag/rdbms/ct6601z3/ct6601z3/trace/ct6601z3_ora_6528.trc.
对应的trace file中记录死锁的相关信息
Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TX-000a0005-00000c72 27 197 X 28 8 X
TX-0001001a-00000ca7 28 8 X 26 132 X
TX-00030001-00000d4e 26 132 X 27 197 X
session 197: DID 0001-001B-00000004 session 8: DID 0001-001C-00000002
session 8: DID 0001-001C-00000002 session 132: DID 0001-001A-00000004
session 132: DID 0001-001A-00000004 session 197: DID 0001-001B-00000004
Rows waited on:
Session 197: obj - rowid = 00038F6D - AAA49tAAEAAABlkAAA
(dictionary objn - 233325, file - 4, block - 6500, slot - 0)
Session 8: obj - rowid = 00038F6D - AAA49tAAEAAABlkAAB
(dictionary objn - 233325, file - 4, block - 6500, slot - 1)
Session 132: obj - rowid = 00038F6D - AAA49tAAEAAABlkAAC
(dictionary objn - 233325, file - 4, block - 6500, slot - 2)
...
RAC全局死锁检测时间
对于单实例数据库而言,死锁的检测在秒级完成,而RAC环境则死锁的检测时间默认达到了1分钟。
对于单实例环境如果出现了死锁,那么马上其中一个进程就被中止,用户可以快速的得到错误返回。而对于RAC而言,死锁的检测并不是实时完成,而是需要60秒左右的时间。
会话1执行:
SQL> create table t_deadlock (id number primary key, name varchar2(30));
Table created.
Elapsed: 00:00:00.12
SQL> insert into t_deadlock values (1, 'a');
1 row created.
Elapsed: 00:00:00.00
SQL> insert into t_deadlock values (2, 'b');
1 row created.
Elapsed: 00:00:00.00
SQL> commit;
Commit complete.
Elapsed: 00:00:00.00
SQL> update t_deadlock set name = 'a1' where id = 1;
1 row updated.
Elapsed: 00:00:00.00
会话2执行:
SQL> set timing on
SQL> update t_deadlock set name = 'b2' where id = 2;
1 row updated.
Elapsed: 00:00:00.00
SQL> update t_deadlock set name = 'a2' where id = 1;
此时,会话2等待会话1的最终操作,下面会话1更新被会话2锁定的行,引发死锁:
SQL> update t_deadlock set name = 'b1' where id = 2;
update t_deadlock set name = 'b1' where id = 2
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
Elapsed: 00:01:00.12
可以看到,死锁的超时检测为1分钟。
而这个死锁的检测时间是可以调整的,Oracle通过隐含参数_lm_dd_interval控制:
SQL> conn / as sysdba
Connected.
SQL> alter system set "_lm_dd_interval" = 30 scope = spfile;
System altered.
SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup
ORACLE instance started.
Total System Global Area 281018368 bytes
Fixed Size 2095672 bytes
Variable Size 104859080 bytes
Database Buffers 167772160 bytes
Redo Buffers 6291456 bytes
Database mounted.
Database opened.
再次测试死锁的检测时间,会话1:
SQL> update t_deadlock set name = 'a1' where id = 1;
1 row updated.
SQL> set timing on
会话2执行更新:
SQL> set timing on
SQL> update t_deadlock set name = 'b2' where id = 2;
1 row updated.
Elapsed: 00:00:00.02
SQL> update t_deadlock set name = 'a2' where id = 1;
会话1执行更新引发死锁:
SQL> update t_deadlock set name = 'b1' where id = 2;
大约30秒后,会话2报错ORA-60:
update t_deadlock set name = 'a2' where id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
Elapsed: 00:00:30.27
在10.2.0.2版本上,Oracle存在一个bug,允许这个参数设置为0,在10.2.0.3以后,这个bug被修正,如果设置为0后,则数据库无法正常启动:
[oracle@node1 ~]$ sqlplus / as sysdba
SQL*Plus: Release 10.2.0.5.0 - Production on Mon Jun 4 07:54:09 2012
Copyright (c) 1982, 2010, Oracle. All Rights Reserved.
Connected to an idle instance.
SQL> startup
ORA-00067: invalid value 0 for parameter _lm_dd_interval; must be at least 1
最后修改隐含参数是Oracle不推荐的,而且修改这个参数势必会影响RAC的正常工作方式导致LDM进程的繁忙度增加,而且可能影响RAC环境的稳定性和可用性。
如果确实对于前台的死锁检查时间要求较高,建议在测试环境中详细测试后再部署到产品环境中。
在11g中,该参数默认值为10
深入研究ITL阻塞与ITL死锁
最近系统死锁连连,google出了很多以前没学过的知识.例如本文,现转载过来备查.
永久链接:http://www.ixdba.com/html/y2007/m05/101-oracle-itl-deadlock.html
首先要说的是,这个问题当初我是真遇到过,然后,就有了汪海的这篇文章:
itl deadlock:http://www.orawh.com/80.html
但是NinGoo 在下面回复说,10g模拟不到这样的情况了,开始我没有太在意,后来再想想,9i这么做的确是不对的,因为还有其它的进程可以释放资源,根本还没有达到死锁的条件。那么,10g就没有itl死锁了吗?也不是的,10g也有,不过是改进了一下,需要把所有的进程阻塞住的时候,才能爆发出死锁。
从死锁的原理上来看,10g是正确的,9i是欠完善的。我们现在完整的分析一下itl等待,以及itl死锁的前因后果,因为这部分在我的新书中也有涉及,属于比较难的一部分,先透露出来,免得大家到时候看书可能看得比较糊涂。
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该表。
【DEADLOCK】Oracle“死锁”模拟
原文地址:【DEADLOCK】Oracle“死锁”模拟 作者:secooler
1.创建两个简单的表t1_deadlock和t2_deadlock,每个表中仅仅包含一个字段a
sys@ora10g> conn sec/sec
Connected.
sec@ora10g> create table t1_deadlock (a int);
Table created.
sec@ora10g> create table t2_deadlock (a int);
Table created.
2.每张表中仅初始化一条数据
sec@ora10g> insert into t1_deadlock values (1);
1 row created.
sec@ora10g> insert into t2_deadlock values (2);
1 row created.
sec@ora10g> commit;
Commit complete.
3.在第一个会话session1中更新表t1_deadlock中的记录“1”为“1000”,不进行提交
sec@ora10g> update t1_deadlock set a = 1000 where a = 1;
1 row updated.
4.在第二个会话session2中更新表t2_deadlock中的记录“2”为“2000”,不进行提交
sec@ora10g> update t2_deadlock set a = 2000 where a = 2;
1 row updated.
5.此时,没有任何问题发生。OK,现在注意一下下面的现象,我们再回到会话session1中,更新t2_deadlock的记录
sec@ora10g> update t2_deadlock set a = 2000 where a = 2;
这里出现了“锁等待”(“阻塞”)的现象,原因很简单,因为在session2中已经对这条数据执行过这个操作,在session2中已经对该行加了行级锁。
注意,这里是“锁等待”,不是“死锁”,注意这两个概念的区别!
检测“锁等待”的方法曾经提到过,请参考《【实验】【LOCK】“锁等待”模拟、诊断及处理方法》http://space.itpub.net/519536/viewspace-605526
sec@ora10g> @lock
lock lock
holder holder lock lock request blocked
username sessid SERIAL# type id1 id2 mode mode BLOCK sessid
-------- ------ ------- ------ ------ ---- ---- ------- ----- -------
SEC 141 6921 TM 15160 0 3 0 0
SEC 141 6921 TX 393231 1672 6 0 1 145
SEC 145 7402 TM 15159 0 3 0 0
SEC 145 7402 TM 15160 0 3 0 0
SEC 145 7402 TX 131077 1675 6 0 0
164 1 TS 3 1 3 0 0
165 1 CF 0 0 2 0 0
165 1 RS 25 1 2 0 0
165 1 XR 4 0 1 0 0
166 1 RT 1 0 6 0 0
167 1 PW 1 0 3 0 0
11 rows selected.
6.我们关注的“死锁”马上就要隆重出场了:在会话session2中,更新t1_deadlock的记录(满足了死锁产生的四个条件了,请慢慢体会)
sec@ora10g> update t1_deadlock set a = 1000 where a = 1;
这里还是长时间等待的现象,但是这里发生了“死锁”!!
细心的您再去第一个会话session1中看一下,原先一直在等待的SQL语句出现如下的现象:
sec@ora10g> update t2_deadlock set a = 2000 where a = 2;
update t2_deadlock set a = 2000 where a = 2
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
更进一步:查看一下alert警告日志文件发现有如下的记录
Mon Aug 10 11:24:29 2009
ORA-00060: Deadlock detected. More info in file /oracle/app/oracle/admin/ora10g/udump/ora10g_ora_25466.trc.
再进一步:看看系统自动生成的trace文件中记录了什么
这个文件包含了5721行的记录信息,截取其中我们关心的前面N多行的内容(结合刚才检测“锁等待”脚本产生的结果分析一下,看看有没有收获):
/oracle/app/oracle/admin/ora10g/udump/ora10g_ora_25466.trc
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, Oracle Label Security, OLAP and Data Mining Scoring Engine options
ORACLE_HOME = /oracle/app/oracle/product/10.2.0/db_1
System name: Linux
Node name: testdb
Release: 2.6.18-53.el5xen
Version: #1 SMP Wed Oct 10 16:48:44 EDT 2007
Machine: x86_64
Instance name: ora10g
Redo thread mounted by this instance: 1
Oracle process number: 14
Unix process pid: 25466, image: oracle@testdb (TNS V1-V3)
*** 2009-08-10 11:24:29.541
*** ACTION NAME:() 2009-08-10 11:24:29.540
*** MODULE NAME:(SQL*Plus) 2009-08-10 11:24:29.540
*** SERVICE NAME:(SYS$USERS) 2009-08-10 11:24:29.540
*** SESSION ID:(145.7402) 2009-08-10 11:24:29.540
DEADLOCK DETECTED
[Transaction Deadlock]
Current SQL statement for this session:
update t2_deadlock set a = 2000 where a = 2
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-00020005-0000068b 14 145 X 15 141 X
TX-0006000f-00000688 15 141 X 14 145 X
session 145: DID 0001-000E-0000037D session 141: DID 0001-000F-0000013D
session 141: DID 0001-000F-0000013D session 145: DID 0001-000E-0000037D
Rows waited on:
7.以上种种现象说明什么?
说明:Oracle对于“死锁”是要做处理的,而不是采用下面提到的“鸵鸟算法”不闻不问。
注意trace文件中的一行如下提示信息,说明一般情况下都是应用和人为的,和Oracle同学没有关系:
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.
8.以上演示了一种“死锁”现象的发生,当然导致死锁发生的情况远远不仅如此。所以在程序设计时一定要好好的进行思考
9.【拓展】
死锁产生的四个必要条件
1)Mutual exclusion(互斥):资源不能被共享,只能由一个进程使用。
2)Hold and wait(请求并保持):已经得到资源的进程可以再次申请新的资源。
3)No pre-emption(不可剥夺):已经分配的资源不能从相应的进程中被强制地剥夺。
4)Circular wait(循环等待条件):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
处理死锁的一般策略
1)鸵鸟算法忽略该问题
2)检测死锁并且恢复
3)仔细地对资源进行动态分配,以避免死锁
4)通过破坏死锁产生呢过的四个必要条件之一,来防止死锁产生
10.总结
死锁对于数据库来说是非常要命的,请多多注意!
对于上面的演示处理的方式:在会话session1中执行rollback进行回滚,会释放导致session2锁等待的锁资源(死锁Oracle已经处理了)。
自相矛盾:一个进程可以自成死锁么
Oracle | 2016-02-03 07:58
崔华,网名 dbsnake
Oracle ACE Director,ACOUG 核心专家
编辑手记:感谢崔华授权我们独家转载其精品文章,也欢迎大家向“Oracle”社区投稿。在新年前,轻松一点,看看崔华这篇小文,通过一个简单的例子,理解Oracle的自制事务、死锁,建议大家动手去测试、尝试,从而从中学到更多的知识。
有朋友问我:“一个transaction会自我死锁吗?也就是自己锁死了自己”。
很凑巧,半个月前我刚好帮同事处理过这种自我死锁的情况。
我们这里来构造一个自我死锁的例子:
select sid from v$mystat
where rownum<2;
SID
———-
362
SQL> create table t1 (id varchar2(10),
amount number(10));
Table created
SQL> insert into t1 values('cuihua',100);
1 row inserted
SQL> commit;
Commit complete
SQL> select * from t1;
ID AMOUNT
———- ———–
cuihua 100
SQL> create procedure p_autonomous is
2 PRAGMA AUTONOMOUS_TRANSACTION;
3 begin
4 update t1 set amount=102
5 where id='cuihua';
6 commit;
7 end;
8 /
Procedure created
SQL> create procedure p_test is
2 begin
3 update t1 set amount=101 where id='cuihua';
4 p_autonomous;
5 commit;
6 end;
7 /
Procedure created
现在只要我执行上述存储过程p_test,就会产生自我死锁,如下所示:
此时alert log里会显示:
ORA-00060: Deadlock detected.
More info in file /u01/app/oracle/admin/ipra/udump/ipra_ora_921828.trc.
从上述trace文件里我们可以看到:
也就是说这里的Blocker是session 362,Waiter也是session 362,典型的自己锁死了自己。
不知道我为什么要这样构造的朋友们看了如下这样一段话就什么都明白了:
TheOracle server provides the ability to temporarily suspend a current transaction and begin another. This second transaction is known as an autonomous transactionand runs independently of its parent. The autonomous or child transaction can commit or rollback as applicable, with the execution of the parent transaction being resumed upon its completion.
The parent may then perform further operations and commit or roll back without affecting the outcome of any operations performed within the child. The child transaction does not inherit transaction context (that is, SET TRANSACTION statements). The transactions are organized as a stack: Only the “top” transaction is accessible at any given time. Once completed, the autonomous transaction is “popped” and the calling transaction is again visible. The limit to the number of suspended transactions is governed by the initialization parameter TRANSACTIONS.
The Oracle server uses similar functionality internally in recursive transactions.
Transactions must be explicitly committed or rolled back or an error ORA-6519 is signaled when attempting to return from the autonomous block.
A deadlock situation may occur where a called and calling transaction deadlock; — this is not prevented, but is signaled by an error unique to this situation. The application developer is responsible for avoiding this situation.
About Me
...............................................................................................................................
● 本文作者:小麦苗,只专注于数据库的技术,更注重技术的运用
● 本文在itpub(http://blog.itpub.net/26736162)、博客园(http://www.cnblogs.com/lhrbest)和个人微信公众号(xiaomaimiaolhr)上有同步更新
● 本文itpub地址:http://blog.itpub.net/26736162/viewspace-2127247/
● 本文博客园地址:http://www.cnblogs.com/lhrbest/p/6005702.html
● 本文pdf版及小麦苗云盘地址:http://blog.itpub.net/26736162/viewspace-1624453/
● 数据库笔试面试题库及解答:http://blog.itpub.net/26736162/viewspace-2134706/
● QQ群:230161599 微信群:私聊
● 联系我请加QQ好友(646634621),注明添加缘由
● 于 2016-09-01 15:00 ~ 2016-10-20 19:00 在魔都完成
● 文章内容来源于小麦苗的学习笔记,部分整理自网络,若有侵权或不当之处还请谅解
● 版权所有,欢迎分享本文,转载请保留出处
...............................................................................................................................
拿起手机使用微信客户端扫描下边的左边图片来关注小麦苗的微信公众号:xiaomaimiaolhr,扫描右边的二维码加入小麦苗的QQ群,学习最实用的数据库技术。