一次Oracle频繁DeadLock问题分析处理经历

一次Oracle频繁DeadLock问题分析处理经历

事件描述

早上十点,客户电话打来,描述了一下业务缓慢,卡的已经不能正常进行下去,几句询问赶紧着手处理。


接过远程后,赶紧连上数据库服务器查看资源状态,发现整体状态趋于正常,询问客户,得知目前业务
已恢复正常,就是刚刚卡的进行不下去。这是怎么回事呢?

图片 1 Computer’s Load Profile

好吧,首先翻看告警日志,发现早上九点到十点钟死锁报错间断进行。

Sat Nov 16 09:07:21 2019
ORA-00060: Deadlock detected. More info in file e:\app\dnsapp\diag\rdbms\xxxxxxdb\xxxxxxdb\trace\xxxxxxdb_ora_4296.trc.
Sat Nov 16 09:07:21 2019
ORA-00060: Deadlock detected. More info in file e:\app\dnsapp\diag\rdbms\xxxxxxdb\xxxxxxdb\trace\xxxxxxdb_ora_6312.trc.
Sat Nov 16 09:07:24 2019
ORA-00060: Deadlock detected. More info in file e:\app\dnsapp\diag\rdbms\xxxxxxdb\xxxxxxdb\trace\xxxxxxdb_ora_6876.trc.
Sat Nov 16 09:10:17 2019
ORA-00060: Deadlock detected. More info in file e:\app\dnsapp\diag\rdbms\xxxxxxdb\xxxxxxdb\trace\xxxxxxdb_ora_4296.trc.
Sat Nov 16 09:20:07 2019
ORA-00060: Deadlock detected. More info in file e:\app\dnsapp\diag\rdbms\xxxxxxdb\xxxxxxdb\trace\xxxxxxdb_ora_6292.trc.
Sat Nov 16 09:20:07 2019
ORA-00060: Deadlock detected. More info in file e:\app\dnsapp\diag\rdbms\xxxxxxdb\xxxxxxdb\trace\xxxxxxdb_ora_2536.trc.
Sat Nov 16 09:35:18 2019
ORA-00060: Deadlock detected. More info in file e:\app\dnsapp\diag\rdbms\xxxxxxdb\xxxxxxdb\trace\xxxxxxdb_ora_5852.trc.

初步推断了一下,早上九点到十点钟死锁问题导致业务卡顿,运行缓慢。
我们都知道死锁是oracle内部自行检测机制,不同于串行锁机制,当系统发现俩个会话互相请求对方锁定
的资源时,oracle就会自动终结串行级别高的(也就是先头请求对方锁定资源)会话请求,并将相关信息
记录在告警日志以及一些相关的trc中。

正常业务系统中,在严密的业务逻辑下,死锁发生的几率很小,但是串行锁所造成的等待也是拖慢业务重
要原因之一。再回过头来看看这个系统中的死锁报错也不算少,怀疑一下可能串行锁的问题也很严重。但
是为什么刚才这样的报错不断,现在却又好了呢?

处理过程

OK,要说诊断性能问题,AWR,ASH肯定是必不可少的,收集报告看看。

 AWR Snap Info

首先,DB Time是快照时间的十倍,这在某些系统中可能不值得一提,但对于今天这个轻量级小系统来说已
经很繁忙了。

AWR Top 5 Events

TOP5来看,已经很明显了,DB CPU、enq: TM – contention、enq: TX - row lock contention、
log file sync基本上占满了,前三个事件都比较集中,通过时间模型,有理由怀疑可能都是由于
栓锁等待问题造成的,而且等待都比较严重了,TM平均等待57550ms,TX平均等待19973ms,

基本上可以理解到快照的后期,串行锁是进行不去的。至于最后一个事件,或许也和今天的问题 也有一定的关系,先mark。

AWR Time Model

数据库时间模型中,数据库总业务时间=DB TIME+ background elapsed time,DB TIME又可以
分为DB CPU+ DB Wait time(no-idle time)。

通过本例中时间模型,sql execute elapsed time所消耗的时间占有百分之92.06的 DB Time(这里
可能会有疑问,为什么DB cpu 还是百分之41.84,俩者加起来超越了百分百DB Time,其实sql exe
cute elapsed time 是个特例,它反应了SQL运行的时间不仅包括消耗在cpu部分,还包括SQL产生
的Wait time部分,俩者发生了一定的重合),所以作一个简单的推测,sql execute elapsed time 
所产生的等待时间部分大部分应该就是TOP5中的锁等待。

这个时候,通过开头的TOP5、TIME Model (有条件的还可以去对比一下基线状态下的LoadProfile)确定了问题的大致方向,我们就可以来个ASH再进一步查看,当然也可以通过AWR后续的sql order by以及segment order by进一步明确相关的SQL及SEGMENT。

ASH Top User Events

   通过ASH的TOP EVENT也正好更AWR的TOP相吻合,background中,日志并行写等待反应了一定的
系统IO问题,虽然很小一部分的比重,但可能在特定的时间阻塞过重要的进程。log file sync 反应了
lgwr不能及时将内存中日志条目写入硬盘上的连接日志组,这有可能是业务commit的次数太多或者存放
联机日志组的硬盘IO瓶颈造成的。

ASH Top Sql With Top Events

    看到这里,基本上可以和业务反馈一些本次锁相关的一些sql和sql引用对象了。但是这个问题依旧
不能说清楚,为什么TM锁等待要比TX锁等待发生次数要多得多?死锁又是怎么产生的呢?如果说按照这个逻
辑来看那么业务应该会一直死锁,为什么就只有今天九点到十点?想着这些客户可能会反馈过来的问题,必须
要把本次问题查清楚。

首先enq:TM-contention发生的条件也挺多的,我们最常见的就是外键缺失索引所导致的,尤其是在oracle9i及以前,那时候关联表的外键列一定要建索引以防更新主表时请求锁定子表所产生大量的enq:TM-contention等待事件造成性能急剧下降;另外我们也可以通过lock table方式主动请求表级锁,如果请求被阻塞就会产生此等待事件。这里还有一些其他的情况就不一一赘述了。

   诊断并消除enq:TM-contention 事件的最有用的信息存在于v$session视图中P1,P2,P3。
既然当时没有捕捉到,那就看ASH吧。

ASH Top Event Pvalues

   这里显示了一个object#为74317,通过数据字典查询为以下对象。

 Object# 74317

   经查询object 74317是一张表,根据以往经验,这张表要不就是当前正在操作表的子表,要不就是当前
表存在DDL操作(如创建索引、修改表列)或者被其他会话以LOCK TABLE 形式获取表级锁。但是业务办的好
好的,大量enq-TM等待事件产生有很大可能就是子表外键索引缺失的问题。

进一步查看ASH中的enq-TM事件TOP 会话为104,51367。

ASH Top Blocking Sessions
Top Blocking Session Detail

  再进一步查询会话详细信息,根据对应的SQL_ID发现16日上午九点多104号会话正在执行主表删除操作。
这里也发现了104号会话被494,1444,1250号会话阻塞。
delete from xx_member_xxxx where id=:1

Primary_Foreign key Relation
 Foreign Key With no index

  经查询可以确认XXX_ member_xxxx 确实是和xxx_order_xxxx有主外键关联关系,其中主表XXX_ me
mber_xxxx ID列被子表xxx_order_xxxx作为外键引用,但是子表上外键列上并未建立相关的索引,根据
以往的经验,这有很大的几率会造成循环死锁。

  问题也许就是这样发生的,删除或更新主表上被子表外键引用的主键列,由于主外键关系的约束,这时操作
主表的会话会请求子表锁以验证主表上数据被子表引用的有效性,而子表本身也会有相应的业务,恰好子表外
键列无相关的索引,这时候enq-TM等待事件就会大量产生。虽然这个算法在9I之后有很大的优化,但是一样
会有很大几率在业务高峰期造成死锁。

分析结果

**

白说无用,我们还是根据ORACLE提供的快照会话信息仔细探究一下今天的死锁是怎样发生的。继续看一下阻塞104号会话的信息。

**

Top Blocked Blocking Session Detail 1

   首先是494,1444,等待事件依旧是enq-TM,执行的SQL大多是INSERT主表(包含ID列),而且根据阻
塞信息可以发现9:04-9:20时间段这俩个会话又被先前阻塞自己的104会话阻塞,这个时候也正好验证了告警
日志中的Deadlock。但是为什么insert和delete主表能够互相阻塞呢?按照正常的事务,这俩个操作应该
不会形成互相阻塞以至于后续的死锁,但是仔细看一下等待事件为enq-TM,等待的子对象都是74317。

**

没错,都是涉及子表外键的约束验证事务,这一点是很有可能发生的,下面我们可以用SCOTT用户简单模拟一下。

**

 Perform Scott's Deadlock

 很容易就可以模拟一下delete主表被insert主表阻塞并造成死锁的情况。当然有的真实的业务场景可能比
这里要复杂的多,这里我们故意在每个会话事务后选择不提交是为了模拟真实场景中主子表数据量很大而且子
表外键列没有索引的情况下。

**

好的,接下来我们继续来看看阻塞104号会话的1250号会话。

**

 Top Blocked Blocking Session Detail2

  这里就很清楚了,先前阻塞104会话的1250会话再等待log file sync,再看看相应的SQL是对子表的UP
DATE操作,并且这个UPDATE除了涉及到外键列也几乎包含了全表的其他96列,可以说全表单行的大批量更新
,这个怪不得产生了LGWR的等待。这个好像就是死锁产生前的一个环节,这次死锁还真是一环又一环啊。

**

好了,结合前面的信息,为了方便理解,我作了一个图示。

**

在这里插入图片描述

  从图示中,我们并不能断言锁的源头是来自哪里,任何一个环节都有它各自的影响因素。就像在实际工作中
, 由于Oracle高效而又复杂的特性,我们对某一个性能问题了解到头来也很难追溯最初的缘由,好在后来
Oracle推出了基于等待事件的调优方法论,我们对性能问题关注的重点也可以转移到性能问题发生各个环节
的制约因素中(也就是等待事件)。




  本例中,logfilesync事件阻塞了子表事务的进行,相应的也阻塞了后来主表操作,这本来就是一种业务
正常进行的情况,但中间硬盘IO问题,子表外键索引问题都无疑加重了锁的持续时间,一来二去,循环往复,
锁的请求不断增加,之前的锁持有会话不断堆积,死锁发生的几率就在这个时候大大增加了。
发布了3 篇原创文章 · 获赞 3 · 访问量 73

猜你喜欢

转载自blog.csdn.net/weixin_42145355/article/details/105406927