Sat*_*hat 12 oracle-10g locked-objects locking
晚了,我一直面临着很多行锁争用。争用的表似乎是一个特定的表。
一般是这样的——
大约 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概念指南节说,“当一个作家修改的行只锁定”。另一个更新同一行的会话将等待第一个会话COMMIT或ROLLBACK之前它可以继续。为了消除这个问题,您可以序列化用户,但这里有一些事情可以将问题减少到不是问题的程度。
COMMIT更频繁。每个都COMMIT释放锁,所以如果你可以批量更新,另一个会话需要同一行的可能性就会降低。UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);应该改写为更具选择性(读更少的锁)UPDATE t1 SET f1=f1+1 WHERE f2=’a’;。当然,如果更改语句仍会锁定表中的大部分行,那么更改只会带来可读性的好处。BULK COLLECT ... FORALL.UPDATE和COMMIT. 例如,如果代码在每次更新后发送电子邮件,请考虑将电子邮件排队并在提交更新后发送。SELECT ... FOR UPDATE NOWAIT或来处理等待WAIT 2。然后,您可以发现无法锁定行并通知用户另一个会话正在修改相同的数据。我将从开发人员的角度提供答案。
在我看来,当您遇到像您描述的那样的行争用时,那是因为您的应用程序中存在错误。在大多数情况下,这种类型的争用是丢失更新漏洞的标志。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% 可以避免的。您应该通过两种主要方法使用锁定来防止它们:乐观锁定和悲观锁定。
您想更新一行。在这种模式下,您将通过请求对该行的锁定(SELECT ... FOR UPDATE NOWAIT语句)来防止其他人修改该行。如果该行已被修改,您将收到一条错误消息,您可以将其优雅地转换为最终用户(该行正在被另一个用户修改)。如果该行可用,请进行修改 (UPDATE),然后在事务完成时提交。
您想更新一行。但是,您不想保持对该行的锁定,可能是因为您使用多个事务来更新该行(基于 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 在内的所有值)。
这个答案可能有资格进入 The Daily WTF。
是的,在跟踪会话并搜索之后USER_SOURCE- 我找到了根本原因