我有一个 PL/SQL 过程,它在循环中提交作业,处理不同的参数(分支)。每个作业都在一个大表上执行插入和更新。由于表的大小,需要加表锁,因此我经常遇到死锁错误,导致每天至少有一个作业失败。
鉴于这种情况,我正在寻求有关如何管理这些作业的建议,以确保它们执行时不会出现死锁。具体来说:
如何让每项工作都等待前一项工作完成后再开始?
有没有办法并行执行这些作业而不会遇到死锁问题,特别是当表大小持续增加时?
任何有关改进作业执行策略以避免死锁的指导将不胜感激。
Oracle 使用行级锁定(除非您有启用了 HCC 压缩的 Exadata)。这意味着表上的 DML 锁会在各个行上被取消(数据块中的每一行都有一个锁位)。因此,如果您遇到锁等待,则说明您有多个作业试图锁定同一行。 并发编程的第一条规则是以这样的方式划分工作负载,即没有两个并发作业需要在同一行上工作。
死锁是指会话 A 和会话 B 锁定不同的行,然后在同一事务中尝试获取对方已锁定的行的锁定。这会创建一个永远无法解决的循环依赖关系,因此 Oracle 选择一个不幸的会话并将其终止。为了减少这种情况的发生,程序员应该遵循以下设计原则:
让您的交易尽可能简短。如果可以的话,请在每次 DML 操作后提交。交易越短意味着循环锁链发生的机会就越少。
一次对单行而不是行集进行操作。在循环中,更新一行、提交、更新下一行、提交等,而不是在单个语句中更新 1000 行。如果单个 DML 锁定多行,则可能会在单个 DML 中发生死锁 - 循环锁定链可能会发展,因为它在锁定后面的行的过程中同时保持对已处理的较早行的锁定。再次强调,我们希望尽可能简短且尽可能原子地持有该锁。受影响的每一行一个事务就可以做到这一点。
如果每个作业中有一系列操作,请确保该顺序每次都朝着相同的方向进行。如果您先接触表 A,然后接触表 B,然后接触表 C,请确保所有并发作业都按照相同的顺序进行。通常是这种情况,但请检查以确定。
仔细考虑外键。插入子行将锁定其父行。删除父行将取消子表上的锁定。如果您同时在父表和子表上进行操作并且有外键,那么这种情况很容易发生死锁。如果您无法通过编程更改来解决它并且无需引用完整性保护也可以,那么您可能在某些时候甚至需要考虑删除 FK 约束。
也可能会出现死锁,因为索引块或表块(小于 23 字节)中没有足够的空间供 Oracle 添加另一个 ITL 槽来处理额外的并发事务。通过对表和索引使用健康的 PCTFREE(默认值 10 通常就足够了)和/或通过将 INITRANS 设置为更高的值,确保您有足够的 ITL 槽。如果我知道我将有 10 个线程同时在同一个表上工作,我喜欢在表和每个索引上设置 INITRANS 10(您必须移动/重建才能生效),以确保永远不会有ITL 短缺。
还有其他可能导致死锁的方式,超出了我在这里所能调查的范围。当死锁确实发生时,Oracle 会生成一个显示锁定链的跟踪文件。请您的 DBA 找到这些跟踪文件之一并将其提供给您。这将帮助您弄清楚如何陷入僵局,并且可以更好地确定解决方案的目标。