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

Sat*_*hat 12 oracle-10g locked-objects locking

晚了,我一直面临着很多行锁争用。争用的表似乎是一个特定的表。

一般是这样的——

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

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

作为数据库开发人员

  • 可以做些什么来消除行锁争用?
  • 是否有可能找出存储过程的哪一行导致这些行锁争用
  • 减少/避免/消除编码问题的一般准则是什么?

如果这个问题感觉过于开放/信息不足,请随时编辑/让我知道 - 我会尽力添加一些额外的信息。


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


数据库版本为 Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit。逻辑流在两个会话中以相同的顺序执行,事务不会保持打开太长时间(或者至少我认为是这样),并且在事务的主动执行期间发生锁定。


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

Lei*_*fel 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';
Run Code Online (Sandbox Code Playgroud)

减少/避免/消除此类编码问题的一般准则是什么?

对锁的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.
  • 减少在第一个UPDATECOMMIT. 例如,如果代码在每次更新后发送电子邮件,请考虑将电子邮件排队并在提交更新后发送。
  • 设计应用程序以通过执行SELECT ... FOR UPDATE NOWAIT或来处理等待WAIT 2。然后,您可以发现无法锁定行并通知用户另一个会话正在修改相同的数据。


Vin*_*rat 7

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

在我看来,当您遇到像您描述的那样的行争用时,那是因为您的应用程序中存在错误。在大多数情况下,这种类型的争用是丢失更新漏洞的标志。AskTom 上的这个线程解释了丢失更新的概念:

丢失更新发生在:

第 1 课:读出 Tom 的员工记录

第 2 课:读出 Tom 的员工记录

会话 1:更新 Tom 的员工记录

第 2 课:更新 Tom 的员工记录

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

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

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk
Run Code Online (Sandbox Code Playgroud)

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


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

1) 悲观锁

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

2)乐观锁定

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

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT
Run Code Online (Sandbox Code Playgroud)

如果查询返回一行,请进行更新。如果没有,这意味着自您上次查询以来有人修改了该行。您必须从头开始重新启动该过程。

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

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;
Run Code Online (Sandbox Code Playgroud)

如果语句没有更新任何行,您就知道有人更改了这一行,您需要重新开始。

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

TL; 博士

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


Sat*_*hat 5

这个答案可能有资格进入 The Daily WTF。

是的,在跟踪会话并搜索之后USER_SOURCE- 我找到了根本原因

  • 不出所料,原因是有缺陷的逻辑
  • 最近,SP 中添加了更新语句。update 语句基本上会更新整个表。显然,有问题的开发人员忘记添加正确的 where 子句来更新所需的语句。
  • 正在更新的表如上所述,是交易量最大的表之一,有大量记录。更新将需要很长时间,令人痛苦的时间。
  • 结果是其他会话无法获得表上的锁,并且会陷入行锁争用中。