Oracle死锁问题小结
Oracle 有三种常见的死锁:
1)ORA-60, enqueue deadlocks;
2)ORA-4020, library cache deadlocks;
3)Deadlocks in RAC
一、ORA-60, enqueue deadlocks
这种死锁很容易重现,按照如下步骤操作:
In sessionA, update tb set col = ‘x’ where pk = ‘aa’
In sessionB, update tb set col = ‘x’ where pk = ‘bb’
In sessionA, update tb set col = ‘x’ where pk = ‘bb’
In sessionB, update tb set col = ‘x’ where pk = ‘aa’
如果两个Session没有commit,那么在上面最后一句sql执行后3秒后,SessionA就会抛出error ORA-60,并且在udump trace 文件中产生死锁提示信息:
---------Blocker(s)-------- ---------Waiter(s)--------- Resource Name process session holds waits process session holds waits TX-00010017-000000ba 8 7 X 9 8 X TX-00020012-000000af 9 8 X 8 7 X session 7: DID 0001-0008-00000002 session 8: DID 0001-0009-00000002 session 8: DID 0001-0009-00000002 session 7: DID 0001-0008-00000002 Rows waited on: Session 8: obj - rowid = 00000CBB - AAAAzjAADAAAAFDAAA Session 7: obj - rowid = 00000CBB - AAAAzjAADAAAAFDAAB |
解释:
1)Resource Name(资源) 其实是指的锁资源(锁是一种资源),在v$resource中可以查询。
2)TX-00020012-000000af
TX 代表事务(transaction),通常指行锁。00020012 和 000000af 分别对应v$lock.id1, v$lock.id2 是16进制的。比如 20012[hex] 是 131090; af[hex] 是 175
2.1) v$lock.id1 表示 回滚段号码左移16位,与slot做或操作得到的结果 “undo segment number << 16 | slot”
确实,我们发现:
v$transaction.xidusn=2 (xidusn =Tx id undo segment number).
x$transaction.xidslot = 18.
所以把十进制的2转换成2进制是10,10左移16位 并且与18做或者操作 等于 131090,计算过程如下:
100000000000000000(10左移16位) 10010(18的二进制) 100000000000010010(上述两个数字或操作结果,你可以在计算器中转成10进制,它=131090) |
2.2) v$lock.id2 应该是 “sequence” 我们发现 v$transaction.xidsqn = 175(xidsqn =Tx id sequence number).
3)Process 是指Oracle process ID(不要和操作系统层的进程ID混淆,它既不是unix上的process id 也不是windows 上的thread ID)
它是v$process.pid 在v$process中,v$process.addr 和 v$session.paddr 做关联
4)session 是 v$session.sid,
5)holds,waits : X表示它现在正hold 了一个排它锁(hold=X),或者正在等待一个排它锁(wait=X)。
6)DID是某种资源ID,第一段可能是instance ID(对于single instance database 它总是1),第2段可能是Oracle 的process id,第3段总是2(除了在 SNP jobprocess 的dump文件中, 不太确定这个段的具体含义)
7)Rows waited on: 对于等待行锁的session, 关于这个行的额外信息就在这段提供。
7.1) CBB[hex] 是 dba_objects.object_id.
7.2) rowid , 这个大家就比较熟悉了,我们通常可以通过它定位到一条记录:SELECT * FROM thetable WHERE ROWID = ‘rowid’
7.3) 如果Rows waited on:下面跟着”no rows”,那么表示它在等待一个表锁,而不是行锁,象下面这样:
---------Blocker(s)-------- ---------Waiter(s)--------- Resource Name process session holds waits process session holds waits TX-00060004-00003155 41 45 X 15 26 S TX-0012000b-00002802 15 26 X 41 45 S session 45: DID 0001-0029-00000002 session 26: DID 0001-000F-00000002 session 26: DID 0001-000F-00000002 session 45: DID 0001-0029-00000002 Rows waited on: Session 26: no row Session 45: no row |
紧跟着死锁图表(deadlock graph 就指上面提供的死锁信息表)的是process 状态导出信息,这些信息以缩进的格式显示状态对象(state object :so)的层次关系。
补救措施:
1)如果 Rows waited on: 下面的Session 后面给出了row ids ,那么只需要简单的等待并退出应用程序,然后考虑是否你有两支程序以不同的顺序操作更新同一张表?
2)如果 Rows waited on: 下面的Session 后面给除了”no row”,呢么这个错误可能是多个session试图更新同一个块(block),而此时这个block的剩余空间太小,Oracle 正在自动增加ITLs((interested transaction lists),针对这种情况,你可能使用更高的INITRANS重建这张表,并设定比较高的PCTFREE。
另一种经常发生的可能性是:你在子表的外健上所在的列上没有索引,并且多个session正在更新父表的主键字段 或者是正在从父表中删除行。两个session 都试图获取子表上共享的表锁 并且它们已经hold了一个行锁(在不同的行上),这种情况下就会导致死锁。
在这种情况下,你可以在子表的外键字段上建立索引,很多站点都提供了脚本来发现没有建立索引的外键(比如:http://www.jlcomp.demon.co.uk/faq/fk_ind.html)
如果你不想这么做,请确保你还没有并且也不打算删除父表中的记录(更新父表的主键是很少见的)或者就象steve adams 建议的那样:ALTER TABLE childtable DISABLE TABLE LOCK. 更多信息可参考:Metalink Note:62365.1
二、ORA-4020, library cache deadlocks
重现方法:按照如下步骤操作:
SQL> create procedure p1 as begin p2; end; 2 / Warning: Procedure created with compilation errors. SQL> create procedure p2 as begin p1; end; 2 / Warning: Procedure created with compilation errors. SQL> alter procedure p1 compile; alter procedure p1 compile * ERROR at line 1: ORA-04020: deadlock detected while trying to lock object YONG.P1 |
上述例子是从Connor McDonald 的”Mastering Oracle PL/SQL: Practical Solutions” 改变而来的,
很显然,在实际应用中,在写代码的过程中,你无法构造出这种彼此调用的代码中而产生的library catch锁。
通常,这种错误是因为无效的对象特别是Trigger,或者Oracle bug 抛出的。E.g., Jonathan Lewis在他的”Practical Oracle8i” 曾报道过这样的例子:
当时正在做partitioned IOT的维护,一个查询抛出了ORA-4020的死锁信息,如下:
ORA-04020: deadlock detected while trying to lock object YONG.P1 -------------------------------------------------------- object waiting waiting blocking blocking handle session lock mode session lock mode -------- -------- -------- ---- -------- -------- ---- 797992F8 7AA06C48 79FA5554 X 7AA06C48 79F8B3D0 X -------------------------------------------------------- |
注意在这个案例中, waiting session 和 blocking session 是同一个,因为它们的Session 地址是一样的.
补救措施:
如果死锁是由于同一个Session引起的,请检查你的死锁的Trace 文件中涉及的表上是否有无效的对象,比如无效的Trigger,如果确实存在,手工编译这些对象.
对于这种死锁,进行event 4020 Trace跟踪将有助于获取更多内部信息,命令如下:
alter session set events ‘4020 trace name processstate forever, level 10′;
如果你不知道或者无法访问这个Session,那么在init.ora文件中添加:
event=’4020 trace name processstate forever, level 10’ ,并重新启动数据库.
那么当下次发生死锁的时候就会产生trace文件,里面应该类似这样的字符串”status=INVL”. 查看”name=”后面的对象名.
如果你经常遇到ORA-4020错误,到metalink寻求帮助或者提个tar.
三、Deadlocks in RAC
在alert.log 中没有具体的错误号,但是ORA-60 and ORA-4020 都有可能。我们只是在alert.log中看到:
“Global Enqueue Services Deadlock detected. More info in file /home/oracle/admin//bdump/_lmd0_
.trc.”
trace文件中显示如下内容:
user session for deadlock lock 0x7553ab14 pid=33 serial=37657 audsid=6450105 user: 246/ O/S info: user: , term: MISFBB10-54, ospid: 4148:5156, machine: \ program: sqlplus.exe application name: SQL*Plus, hash value=3669949024 Current SQL Statement: update test set a = :"SYS_B_0" where a = :"SYS_B_1" Global Wait-For-Graph(WFG) at ddTS[0.28] : BLOCKED 0x7553ab14 5 [0xf001d][0x8353],[TX] [2162689,7995] 0 BLOCKER 0x69cabb5c 5 [0xf001d][0x8353],[TX] [1769474,4478] 1 BLOCKED 0x69cabc30 5 [0x70014][0xe908],[TX] [1769474,4478] 1 BLOCKER 0x7e1424e8 5 [0x70014][0xe908],[TX] [2162689,7995] |
根据Note:262226.1 ,这种情况曾经发生在这样的背景下:
OS:linux
Oracle version: 9.2.0.7.0 RAC
客户端程序所在的OS:windows pc
解读:
Global Wait-For-Graph(WFG) at ddTS[0.28] : 下面有4行信息,解读如下:
行号 阻塞/被阻塞 Lock pointers 锁模式 id1 id2 锁名称 transaction_id0,id1 DB实例 1 BLOCKED 0x7553ab14 5 [0xf001d][0x8353],[TX] [2162689,7995] 0 2 BLOCKER 0x69cabb5c 5 [0xf001d][0x8353],[TX] [1769474,4478] 1 3 BLOCKED 0x69cabc30 5 [0x70014][0xe908],[TX] [1769474,4478] 1 4 BLOCKER 0x7e1424e8 5 [0x70014][0xe908],[TX] [2162689,7995] |
Note:262226.1 解释了相关意义:
1)Lock Name(TX)后面的数值意义:比如 [2162689,7995] 方括号中的两个数字是 v$ges_blocking_enqueue中的transaction_id0 和 transaction_id1 ,分别等效于v$dlm_locks中的相应的列。(但我不知道transaction number 的意义。这看起来不像是v$transaction中 xidusn.xidslot.xidsqn的组合)
2)此外”BLOCKED” 或者 “BLOCKER”后面跟的十六进制数值是v$ges_blocking_enqueue中的handles (lock pointers),
3)锁模式后面的两个十六进制数值分别是 v$lock中的id1和id2(请参考http://yong321.freeshell.org/oranotes/LocksWhere2FindMoreInfo.txt 理解它们的意义 )
上述案例说明了这样一个事实:
instance 0上的第一个进程请求一个mode5的Tx lock [note2](第1行),并且被instance 1上的第2个进程阻塞(第2行),
而instance 1上的第2个进程又请求一个TX锁(第3行),而这个锁有反过来被instance 0上的第一个进程阻塞(第4行)
上面的trace文件显示了死锁中的SQL,但在有些案例中是没有SQL的[note3],如下面的例子:
Global Wait-For-Graph(WFG) at ddTS[0.15] : BLOCKED 0x70d17058 5 [0xf4dd2a46][0x57e4077],[LB] 30268 0 BLOCKER 0x70d16594 5 [0xf4dd2a46][0x57e4077],[LB] 30268 0 |
这个死锁是“B-namespace library cache”锁请求,并且被自己阻塞。process id是30268(这里是单个数值,不是在方扩号中的两个数值),很不幸,这里丢失了SQL
Note:262226.1 指出添加SQL到WFG trace将是有帮助的,如果死锁可以在同一instance中重现(正如这个案例中,最后一列(instance)是相同的),可以先取消数据库集群,
在非RAC环境中的死锁信息比global WFG更加有帮助,因为这是在所有相关的进程都在同一节点的时候创建的。
备注:
________________________
[note1]
我们经常建议应用程序写代码的时候应该按照相同的顺序对表进行DML操作。也就是说,程序1应该先更新tab1X 然后再更新tb1Y, 而程序2也应该按照相同的顺序进行更新。
但是还有很多其他情况,也应该遵循这个规则。考虑如下情况:
if conditionA, update tblX if conditionB, update tblY if conditionC, update tblX if conditionD, update tblY |
如果这个单一程序(没有程序2)是可能在多个进程中跑的,根据每个进程遇到的判断条件不一样,也可能引发死锁。
[note2]
我还从来没见过在Global WFG’s中,锁模式不等于5的情况,这个数值可能Oracle 代码中偶尔也存在的hardcoded情况吗?
[note3]
根据 Dusan Bolek (message 10 of
http://groups.google.com/group/comp.databases.oracle.server/browse_frm/thread/a1e9aacfc4e16e25/),
如果死锁发生在多哥instance之间,那么SQL就会被提供,而在同一个instance上的话就没有提供。
编注:
______________
这篇小结是在美国工作的黄勇写的,我这里只是把他翻译一下。
原文参考:http://yong321.freeshell.org/computer/deadlocks.txt
有关锁的概念还可以参考: http://www.4studio.cn/www/51/2008-04/2672.html
关于作者:
昵称:Jack.shang 档案信息:jack.shang 一位从技术走向管理,再从管理走向市场的普通行者 联系方式:你可以通过syfvb@hotmail.com联系作者 点击查看Jack.shang发表过的所有文章... 本文永久链接: http://blog.retailsolution.cn/archives/2069 |
对本文的评价: