暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

oracle 死锁

原创 逆风飞翔 2021-12-15
1281

(一)什么是死锁?
所谓死锁,是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。Oracle对于“死锁”是要做处理的,而不是不闻不问。
(二)死锁的trace文件
Oracle中产生死锁的时候会在告警日志(alert_$ORACLE_SID.log)文件中记录死锁的相关信息,无论单机还是RAC环境都有Deadlock这个关键字,而且当发生死锁时都会生成一个trace文件,这个文件名在告警日志文件中都有记载。由于在RAC环境中,是由LMD(Lock Manager Daemon)进程统一管理各个节点之间的锁资源的,所以,RAC环境中trace文件是由LMD进程来生成的。
在RAC环境中,告警日志的形式如下所示:
1Mon Jun 20 10:10:56 2016
2Global Enqueue Services Deadlock detected. More info in file
3 /u01/app/Oracle/diag/rdbms/raclhr/raclhr2/trace/raclhr2_lmd0_19923170.trc.


RAC环境下死锁发生的时候,在系统的BDUMP目录下的DIAG进程的TRACE文件中也会有相关的记录的。
首先 ps -ef|grep diag 找到当前实例的 DIAG 进程的操作系统进程号,
然后根据 <SID>_diag_<OSPID>.trc 来找到对应的TRACE文件,然后翻到死锁发生的时间点

OBJ =252920  ROWID=AAA9v4AAaABuggAAAA
对象ID 通过 
SELECT * FROM DBA_OBJECTS WHERE OBJECT_ID=?
SELECT * FROM T WHERE ROWID ='?'


三、死锁的定位方法
通过检查数据库表,能够检查出是哪一条语句被死锁,产生死锁的机器是哪一台。
 
1)用dba用户执行以下语句
select username,lockwait,status,machine,program from v$session where sid in
(select session_id from v$locked_object)

如果有输出的结果,则说明有死锁,且能看到死锁的机器是哪一台。字段说明:
Username:死锁语句所用的数据库用户;
Lockwait:死锁的状态,如果有内容表示被死锁。
Status: 状态,active表示被死锁
Machine: 死锁语句所在的机器。
Program: 产生死锁的语句主要来自哪个应用程序。
2)用dba用户执行以下语句,可以查看到被死锁的语句。
select sql_text from v$sql where hash_value in (select sql_hash_value from v$session where sid in
(select session_id from v$locked_object))
 
四、死锁的解决方法
一般情况下,只要将产生死锁的语句提交就可以了,但是在实际的执行过程中。用户可能不知道产生死锁的语句是哪一句。可以将程序关闭并重新启动就可以了。经常在Oracle的使用过程中碰到这个问题,所以也总结了一点解决方法。
1)查找死锁的进程:
sqlplus "/as sysdba" (sys/change_on_install)
SELECT s.username,l.OBJECT_ID,l.SESSION_ID,s.SERIAL#,l.ORACLE_USERNAME,l.OS_USER_NAME,l.PROCESS 
FROM V$LOCKED_OBJECT l,V$SESSION S WHERE l.SESSION_ID=S.SID;
 
2)kill掉这个死锁的进程:
alter system kill session ‘sid,serial#’; (其中sid=l.session_id)
 
3)如果还不能解决:
select pro.spid from v$session ses,v$process pro where ses.sid=XX and ses.paddr=pro.addr;
 
其中sid用死锁的sid替换: exit
ps -ef|grep spid
 
其中spid是这个进程的进程号,kill掉这个Oracle进程
select A.SQL_TEXT, B.USERNAME, C.OBJECT_ID, C.SESSION_ID, B.SERIAL#, C.ORACLE_USERNAME,C.OS_USER_NAME,C.Process,''''||C.Session_ID||','||B.SERIAL#||''' from v$sql A, v$session B, v$locked_object C where A.HASH_VALUE = B.SQL_HASH_VALUE and B.SID = C.Session_ID


在单机环境中,告警日志的形式如下所示:
1Mon Jun 20 12:10:56 2016
2ORA-00060: Deadlock detected. More info in file /Oracle/app/Oracle/diag/rdbms/dlhr/dlhr/trace/dlhr_ora_16973880.trc.

通常来讲,对于单机环境,当有死锁发生后,在trace文件中会看到如下的日志信息:

当看到trace文件时,需要确认一下锁的类型,是两行还是一行,是TX还是TM,如果只有一行,那么说明是同一个SESSION,可能是自治事务引起的死锁。
对于RAC环境,当有死锁发生后,在trace文件中会看到如下的日志信息:

图 3-17 RAC环境下的死锁
(三)死锁的检测时间
死锁的检测时间是由隐含参数“_LM_DD_INTERVAL”来控制的,在Oracle 11g中,隐含参数“_LM_DD_INTERVAL”的值默认为10s,而在Oracle 10g中,该参数的值默认为60s。
(四)死锁的分类
有人的地方就有江湖,有资源阻塞的地方就可能有死锁。
Oralce中最常见的死锁分为:行级死锁(Row-Level Deadlock)和块级死锁(Block-Level Deadlock)
,其中,行级死锁分为
①主键、唯一索引的死锁(会话交叉插入相同的主键值),
②外键未加索引,
③表上的位图索引遭到并发更新,
④常见事务引发的死锁(例如,两个表之间不同顺序相互更新操作引起的死锁;同一张表删除和更新之间引起的死锁),
⑤自治事务引发的死锁。块级死锁主要指的是ITL(Interested Transaction List)死锁。
死锁分类图如下所示:

二,在外键上没有加索引引起的死锁

客户的10.2.0.4 RAC for AIX环境频繁出现ORA-60死锁问题,导致应用程序无法顺利执行。
经过一系列的诊断,发现最终问题是由于外键上没有建立索引所致,由于程序在主子表上删除数据,缺少索引导致行级锁升级为表级锁,最终导致大量的锁等待和死锁。
下面通过一个例子简单模拟一下问题:
SQL> create table t_p (id number primary key, name varchar2(30));
Table created.
SQL> create table t_f (fid number, f_name varchar2(30), foreign key (fid) references t_p);
Table created.
SQL> insert into t_p values (1, 'a');
1 row created.
SQL> insert into t_f values (1, 'a');
1 row created.
SQL> insert into t_p values (2, 'b');
1 row created.
SQL> insert into t_f values (2, 'c');
1 row created.
SQL> commit;
Commit complete.
SQL> delete t_f where fid = 2;
1 row deleted.
这时在会话2同样对子表进行删除:
SQL2> delete t_f where fid = 1;
1 row deleted.
回到会话1执行主表的删除:
SQL> delete t_p where id = 2;
会话被锁,回到会话2执行主表的删除:
SQL2> delete t_p where id = 1;
会话同样被锁,这时会话1的语句被回滚,出现ORA-60死锁错误:
delete t_p where id = 2
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
SQL> rollback;
Rollback complete.
将会话1操作回滚,会话2同样回滚并建立外键列上的索引:
1 row deleted.
SQL2> rollback;
Rollback complete.
SQL2> create index ind_t_f_fid on t_f(fid);
Index created.
重复上面的步骤会话1删除子表记录:
SQL> delete t_f where fid = 2;
1 row deleted.
会话2删除子表记录:
SQL2> delete t_f where fid = 1;
1 row deleted.
会话1删除主表记录:
SQL> delete t_p where id = 2;
1 row deleted.
会话2删除主表记录:
SQL> delete t_p where id = 1;
1 row deleted.
所有的删除操作都可以成功执行,关于两种情况下锁信息的不同这里就不深入分析了,重点就是在外键列上建立索引。
虽然有一些文章提到过,如果满足某些情况,可以不在外键列上建立的索引,但是我的观点一向是,既然创建了外键,就不要在乎再多一个索引,因为一个索引所增加的代价,与缺失这个索引所带来的问题相比,是微不足道的。

(五)块级死锁的介绍
有关每种行级死锁的介绍和模拟可以参考本节后的BLOG连接,这里简单介绍一下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等待表现出的等待事件为“TX - allocate ITL entry”,根据MOS(Troubleshooting waits for 'enq: TX - allocate ITL entry' (Doc ID 1472175.1)提供的解决办法,需要修改表和索引的参数(PCTFREE和INITRANS),修改使用的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;



--------查询死锁

create procedure sp_who_lock
as
begin
declare @spid int
declare @blk int
declare @count int
declare @index int
declare @lock tinyint
set @lock=0
create table #temp_who_lock
(
id int identity(1,1),
spid int,
blk int
)
if @@error<>0 return @@error
insert into #temp_who_lock(spid,blk)
select 0 ,blocked
from (select * from master..sysprocesses where blocked>0)a
where not exists(select * from master..sysprocesses where a.blocked =spid and blocked>0)
union select spid,blocked from master..sysprocesses where blocked>0
if @@error<>0 return @@error
select @count=count(*),@index=1 from #temp_who_lock
if @@error<>0 return @@error
if @count=0
begin
select N'没有阻塞和死锁信息'
return 0
end
while @index<=@count
begin
if exists(select 1 from #temp_who_lock a where id>@index and exists(select 1 from #temp_who_lock where id<=@index and a.blk=spid))
begin
set @lock=1
select @spid=spid,@blk=blk from #temp_who_lock where id=@index
select N'引起数据库死锁的是: '+ CAST(@spid AS VARCHAR(10)) + N'进程号,其执行的SQL语法如下'
select @spid, @blk
dbcc inputbuffer(@spid)
dbcc inputbuffer(@blk)
end
set @index=@index+1
end
if @lock=0
begin
set @index=1
while @index<=@count
begin
select @spid=spid,@blk=blk from #temp_who_lock where id=@index
if @spid=0
select N'引起阻塞的是:'+cast(@blk as varchar(10))+ N'进程号,其执行的SQL语法如下'
else
select N'进程号SPID:'+ CAST(@spid AS VARCHAR(10))+ N'被' + N'进程号SPID:'+ CAST(@blk AS VARCHAR(10)) +N'阻塞,其当前进程执行的SQL语法如下'
dbcc inputbuffer(@spid)
dbcc inputbuffer(@blk)
set @index=@index+1
end
end
drop table #temp_who_lock
return 0
end
exec sp_who_lock




「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论