常见的死锁是如何产生的,如何避免
概述:
什么场景下回发生 00060 死锁问题:
一般情况下,数据库自身发生死锁的情况很少,一般情况都是因为应用本身调用问题导致的 00060异常 。
比如说有两个会话sid,分别为 138 和136,这两个会话都要对 6677 和 7788 两个人加工资,但是执行的顺序不一样,操作分别是:
-- 会话session号 Session 1 (sid = 136), Session 2 (sid = 138)
-- 执行的语句 Session 1 (sid = 136)
update emp set sal=sal+100 where empno=6677;
update emp set sal=sal+100 where empno=7788;
-- 执行的语句 Session 2 (sid = 138)
update emp set sal=sal+100 where empno=7788;
update emp set sal=sal+100 where empno=6677;
ORA-00060:deadlock detected while waiting for resource
这样我们就成功的触发了一个ORA-00060。
出现这个问题,我们可以查看Oracle日志,日志路径:$ORACLE_BASE/diag/rdbms/org11/ora11/trace/alert*.log
可以从告警日志中看到很多类似如下的日志:
ORA-00060:Deadlock detected.More info in file /home1/oracle/diag/rdbms/ora11g/ora11g/trace/ora11g_ora_14757.trc
我们看下对饮的trc日志,主要看Deadlock graph,其中:可以看到136和138互相死锁,session 138(也就是 session2,sid=138)等着要 ROWID=AAAMfAAAAgAAA的行锁,而 session136(也就是 session1,sid=136)等着要ROWID=AAAMfPAAEAAAAgAAL 的行。
处理方式:
1. 给资源编号,然后按照固定的顺序进行访问。
简单的来说,就是先改编号小的,在改编号大的。当然,反着来也可以。
-- 会话session号 Session 1 (sid = 136), Session 2 (sid = 138)
update emp set sal=sal+100 where empno=6677;
update emp set sal=sal+100 where empno=7788;
Waiting...
update emp set sal=sal+100 where empno=7788;
commit/rollback;
update emp set sal=sal+100 where empno=6677;
在这里,工资增加两次,但是 session 2 被 session 1 阻塞了,对于用户体验来说,感受不好。
如果 session 1 一直不结束事务,session 2 只能一直等下去,这样比deadlock 后,Oracle 程序本身出面调停还要糟糕。
2. 可以在 select … for update nowait 语句测试一下需要更改的行是否被锁定
如果没有被锁定,那这个语句会马上给这行加锁,如果已经加锁那就马上返回:ORA-00054:resource busy and acquire with NOWAIT specified
,如下表所示:
-- 会话session号 Session 1 (sid = 136), Session 2 (sid = 138)
select * from emp where empno in(6677,7788) for update nowait;
select * from emp where empno in(6677,7788) for update nowait;
ORA-00054:resource busy and acquire with NOWAIT specified
update emp set sal=sal+100 where empno=6677;
update emp set sal=sal+100 where empno=7788;
方法一和方法二都存在一定的问题,特别是在ND代码中如果使用方法二那么修改起来工作量太大,但是如果我们不处理,Oracle有自动检测死锁并且回滚事务的功能,也就是说之前的会话中136 和138 有一个会成功,一个会回滚,返回失败,这样就保证了数据的一致性。
总结:
对应上面两处处理方式。感觉都不好,毕竟现网这种场景较少。而且这种死锁不是永久性的一直卡死在这,Oracle会检测到这种死锁的,并且检测到后会自己回滚,所以直接交给Oracle即可。
附相关的查询SQL:
查询死锁:
select t2.username,t2.sid,t2.seria#,t2.logon_time from v$locked_object t1,v$session t2 where t1.session_id = t2.sid order by t2.logon_time;
根据 sid 查询对应的SQL语句,比如第一点查询出 sid 为136 和138 的死锁结果:
select sql_text from v$session a,$sqltext_with_newlines b where DECODE(a.sql_hash_value,0,prev_hash_value,sql_hash_value) = b.hash_value and a.sid in ('136','138') order by piece;
查看处于等待状态的SQL语句:
select a.spid,c.EVENT,b.LOGON_TIME,d.SQL_TEXT,a.PROGRAM from v$process a,v$session b,v$session_wait c,v$sql d where a.ADDR = b.PADDR and b.SID = c.SID and b.SQL_HASH_VALUE = D.HASH_VALUE and c.EVENT NOT LIKE '%SQL*Net%' and c.EVENT NOT LIKE '%smon%' and c.EVENT NOT LIKE '%jopq%' and c.EVENT NOT LIKE '%ipc%'
查看目前是否有“长时间持有锁未释放”,必要的情况下可以用对应的 command 杀死 session:
select I.BLOCK,ILMODE,I.REQUEST,I.TYPE,I.ID1,I.CTIME,s.SID,s.SERIAL#, T.SQL_TEXT,p.SPID,'ALTER SYSTEM KILL SESSION'"||S.SID||','||s.serial#||"',' Command from v$lock i,v$SESSION s,v$SQL t,v$process p where I.ID1 in (select id1 from v$lock where block = 1) and I.SID = s.SID and (t.hash_value = DECODE(s.sql_hash_value,0,s.prev_hash_value,s.sql_hash_value)) and t.address = DECODE(a.sql_hash_value,0,s.prev_hash_addr,s.sql_address)) and p.ADDR = s.PADDR order by I.ID1,I.CTIME desc;
杀死进程(396为sid,60589为serial#),该条可以结合第一点进行:
alter system kill session '396,60589';