跟踪,调试和修复行锁争用


12

迟到了,我遇到了很多行锁争用。争用的表似乎是特定的表。

通常就是这样-

  • 开发人员1从Oracle Forms前端屏幕开始事务
  • 开发人员2使用同一屏幕从不同的会话开始另一笔交易

大约5分钟后,前端似乎没有反应。检查会话显示行锁争用。每个人都抛出的“解决方案”是终止会话:/

作为数据库开发人员

  • 如何消除行锁争用?
  • 是否有可能找出存储过程的哪一行引起这些行锁争用
  • 减少/避免/消除此类问题的通用准则是什么?

如果您觉得此问题过于开放/信息不足,请随时进行编辑/让我知道-我会尽力补充一些其他信息。


有问题的表有很多插入和更新,我想说它是最繁忙的表之一。SP相当复杂-为简化起见-它从各种表中获取数据,将其填充到工作表中,在工作表上发生许多算术运算,并且将工作表的结果插入/更新到有问题的表中。


数据库版本为Oracle Database 10g企业版10.2.0.1.0版-64位。逻辑流在两个会话中以相同的顺序执行,事务未保持打开状态的时间过长(或至少我认为如此),并且在事务的主动执行过程中发生了锁定。


更新:表行数比我预期的要大,大约为310万行。另外,在跟踪会话后,我发现该表的几个更新语句没有利用索引。为什么会这样-我不确定。where子句中引用的列已建立索引。我目前正在重建索引。


1
@Sathya-您能详细说明存储过程的复杂性吗?可疑表是否经过严格的更新或插入?
CoderHawk

外键在这里发挥作用吗?(有时需要一个索引)是什么版本的数据库?两个会话中的逻辑流程是否以相同的顺序执行?交易是否长期保持“开放”状态?锁定是在用户思考时间还是在事务的主动执行过程中发生的?
ik_zelf 2011年

@桑迪我已经更新了问题
Sathyajith Bhat 2011年

@ik_zelf我已经更新了问题
Sathyajith Bhat 2011年

1
我不清楚这是为什么会出现问题-Oracle确实在做应该做的事情,即对单行的访问进行序列化。如果某人有该行,则可以读取它的先前版本,但要写该行,必须等待他们释放锁。唯一的“解决办法”是:a)不要四处走走和/ COMMITROLLBACK在合理的时间内;或b)安排使同一个人不一定总是在同一时间想要同一行。
盖乌斯

Answers:


10

是否有可能找出导致这些行锁争用的存储过程的哪一行?

并非完全正确,但是您可以获取引起锁定的SQL语句,并依次确定过程中的相关行。

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

减少/避免/消除编码中的此类问题的通用指南是什么?

Oracle概念指南》中有关锁的部分说:“只有在写入者修改后,行才被锁定。” 然后,更新同一行的另一个会话将等待第一个会话到达COMMITROLLBACK继续之前。为了消除此问题,您可以序列化用户,但是有些事情可以将问题减少到可能不是问题的程度。

  • COMMIT更频繁。每个COMMIT释放锁都会锁定,因此,如果您可以批量进行更新,则可以减少另一个会话需要同一行的可能性。
  • 确保不更新任何行而不更改它们的值。例如,UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);应重写为更具选择性的(读取较少的锁)UPDATE t1 SET f1=f1+1 WHERE f2=’a’;。当然,如果更改语句仍将锁定表中的大多数行,则更改将仅具有可读性。
  • 确保使用序列而不是锁定表以将一个值添加到当前最高值。
  • 确保您没有使用导致不使用索引的函数。如果需要该函数,请考虑使其成为基于函数的索引。
  • 集合思考。考虑运行一个运行PL / SQL块进行更新的循环是否可以重写为单个更新语句。如果不是这样的话,也许可以使用批量处理BULK COLLECT ... FORALL
  • 减少在第一个UPDATE和第二个之间完成的工作COMMIT。例如,如果代码在每次更新后发送电子邮件,请考虑对电子邮件进行排队并在提交更新后将其发送。
  • 通过执行SELECT ... FOR UPDATE NOWAIT或来设计应用程序以处理等待WAIT 2。然后,您可以发现无法锁定行并通知用户另一个会话正在修改相同的数据。

7

我将从开发人员的角度提供答案。

我认为,当遇到诸如您描述的争用之类的行争用时,这是因为您的应用程序中存在一个错误。在大多数情况下,此类竞争是更新丢失漏洞的迹象。AskTom上的线程解释了丢失更新的概念:

在以下情况下会丢失更新:

第一节:读出汤姆的员工记录

第二节:读出汤姆的员工记录

第一场:更新汤姆的员工记录

第二节:更新汤姆的员工记录

会话2将覆盖WRITE会话1的更改,而不会看到它们-导致更新丢失。

您遇到了丢失更新的一个讨厌的副作用:由于会话1尚未提交,因此会话2被阻止。但是,主要问题是会话2盲目更新记录。假设两个会话都发出以下语句:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

在这两个语句之后,会话1的修改已被覆盖,而没有通知会话2该行已被会话1修改。


丢失更新(以及竞争副作用)永远不会发生,这是100%可以避免的。您应该通过两种主要方法使用锁定来防止它们:乐观锁定和悲观锁定

1)悲观锁定

您要更新一行。在这种模式下,您将通过请求对该行进行锁定来防止其他人修改该行(SELECT ... FOR UPDATE NOWAIT语句)。如果该行已经被修改,您将收到一条错误消息,您可以将其优雅地转换为最终用户(该行正在被其他用户修改)。如果该行可用,请进行修改(UPDATE),然后在事务完成时提交。

2)乐观锁

您要更新一行。但是,您不想维护该行的锁定,可能是因为您使用了多个事务来更新该行(基于Web的无状态应用程序),或者您不想让任何用户将锁定时间太长(这可能会导致其他人被阻止)。在这种情况下,您不会立即请求锁定。您将使用标记来确保在发布更新时该行没有更改。您可以缓存所有列的值,或者可以使用自动更新的时间戳列或基于序列的列。无论您选择哪种方式,当您要执行更新时,都可以通过发出以下查询来确保该行上的标记未更改:

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

如果查询返回一行,请进行更新。如果不是,则表示自您上次查询以来,某人已修改了该行。您必须从头开始重新启动该过程。

注意:如果您完全信任访问数据库的所有应用程序,则可以依靠直接更新进行乐观锁定。您可以直接发出:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

如果该语句没有更新任何行,则说明您知道有人更改了该行,因此您需要重新开始。

如果所有应用程序都同意该方案,那么您将永远不会被其他人阻止,并且可以避免盲目更新。但是,如果您不事先锁定该行,则在另一个应用程序,批处理作业或直接更新未实现乐观锁定的情况下,仍然容易受到不确定锁定的影响。这就是为什么我建议始终锁定行的原因,无论您选择哪种锁定方案(性能锁定都可以忽略不计,因为在锁定行时您检索了包括rowid在内的所有值)。

TL; DR

  • 在没有锁定的情况下更新行会使应用程序潜在地“冻结”。如果对数据库的所有DML都实现了乐观或悲观锁定,则可以避免这种情况。
  • 验证SELECT语句返回的值与任何以前的SELECT一致(以避免任何丢失的更新问题)

5

该答案可能符合“每日WTF”中的条目要求。

正确,在跟踪会话并进行搜索之后USER_SOURCE-我找到了根本原因

  • 毫无疑问,原因是逻辑错误
  • 最近,在SP中添加了一条更新语句。更新语句将基本上更新整个表。显然,有问题的开发人员忘记了添加正确的where子句来更新所需的语句。
  • 如上所述,要更新的表是交易最多的表之一,并且具有大量记录。更新将花费很长的时间。
  • 结果是其他会话无法在表上锁定,而是处于行锁争用状态。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.