如何避免数据库死锁?

Posted

技术标签:

【中文标题】如何避免数据库死锁?【英文标题】:How to avoid database deadlocks? 【发布时间】:2013-01-02 23:53:08 【问题描述】:

某些数据库功能,例如SELECT ... FOR UPDATEON DELETE CASCADE,由于数据库没有指定将使用什么锁定顺序,因此隐含地容易发生死锁。我发现 two discussions 暗示 SQL 标准没有指定这种行为,更不用说具体的实现了。因此,我是在我们无法控制锁定顺序的假设下进行操作的(至少,如何做到这一点并不明显)。

如果我们不能依赖锁定顺序,我们应该

如果我们不应该避免僵局(你将不得不非常努力地说服我相信这一点)那么我们应该怎么做?

这个问题与数据库无关,所以请不要问我使用的是哪个数据库。

【问题讨论】:

死锁是由应用程序的故障引起的。您只需要整理您的应用程序并正确使用数据库。不要试图“滚动你自己的”并发控制——把它留给数据库。这是关于 Oracle 解除锁定处理的一个有趣的 AskTom 问题。 asktom.oracle.com/pls/asktom/… 我想说的是:您不需要知道“级联删除”使用什么锁定顺序。 DBMS 在删除和级联期间负责锁定和并发,如果需要取消事务/回滚。就您而言,删除和级联是原子操作。 @LordPeter,我的目标是编写一个保证运行没有死锁的应用程序。我没有看到数据库保证不会发生死锁的迹象,而如果我推出自己的并发控制,我可以做出这样的保证。回滚事务是不可接受的;一开始就不应该出现死锁。 我没有正确传达这个问题。死锁按定义是一个应用程序错误,DBMS 将通过狙击/杀死其中一个死锁会话来解决该错误。只有当应用程序有问题时才会发生死锁。如果必须的话,可以使用自己的并发控制,但微软、甲骨文、IBM 和一大群 OSS 人员几十年来一直在改进他们的并发控制 - 使用他们为您构建的东西! @LordPeter,如果应用程序控制建立锁的顺序,死锁只能是应用程序错误。要么提供我可以预期的锁定顺序的解释,要么提供一个有问题的应用程序行为的示例,以便我知道应该避免什么。 【参考方案1】:

只是不要使用那些可能导致死锁的功能。 ON DELETE CASCADE 可以以强制顺序的方式重写,从而避免死锁。

SELECT ... FOR UPDATE 专门设计用于避免锁定 - 它允许您选择并锁定一行,这样您就可以在所有线程上保持一致的顺序。

您必须小心使用它,如果您不了解所有更新的锁定顺序,可能会导致死锁。

【讨论】:

我知道如何替换ON DELETE CASCADE,但是你用什么替换SELECT ... FOR UPDATE 还有没有其他容易死锁的SQL特性需要这样重写? @Gili - 可能有许多非标准的 sql 功能(cascade 只是其中之一)。这取决于您使用的是哪个堆栈。 我扫描了 SQL 99 标准。我相信cascade 是标准化的,因为它被多次提及,但是我没有看到任何关于 any 功能的锁定顺序的讨论。这正常吗? 更新:根据***.com/a/112256/14731,您无法防止死锁,只能降低其频率。所以也许我们最好使用ON DELETE CASCADE,以便为数据库提供更多信息,并希望它在后台使用适当的锁定顺序。【参考方案2】:

几年后,我正在修改接受的答案以声明database deadlocks cannot be prevented。

如果您足够幸运能够将数据库操作分解为一次只与一个表进行交互(这并不总是可能的),那么您将不得不在性能不佳和死锁的可能性之间做出选择。选择你的毒药。

【讨论】:

语句级别不能确定锁定顺序不是更接近事实吗?它当然可以通过语句的顺序在块级别确定。很简单,任何级联删除都必须作为过程的一部分(在语句之间适当地保持锁)而不是根据约束来完成。 @Steve 我假设您对块的定义是语句的集合。如果是这样,如果不能在语句级别确定锁定顺序,如何在块级别确定锁定顺序?添加锁无济于事,因为个别语句会自行触发死锁。 (本讨论假设单个语句必须针对多个表,并且不能跨多个语句划分。) 单个语句不能(除非我严重遗漏某些内容)自行触发死锁。它总是需要两个单独的语句,在单独的事务中,同时进行交互。此外,一条语句必须 选择多个表 - 我可以先锁定一个表,然后再锁定另一个,然后然后 开始涉及这对表的工作。我不怀疑这种方法,如果始终如一地应用,很快就会让开发人员求饶,但如果死锁绝对不能被提供,并发编程的内在复杂性就是如此。 @Steve 看到您无法控制语句的执行计划,并且同一语句可能会因在不同线程中运行的自身(具有不同的执行计划)而死锁,您本质上提倡的是在SERIALIZABLE 事务隔离中运行。此外,我要指出,如果不冒竞争条件的风险,就无法分解某些语句(例如SELECT employee.name, employer.name FROM employee ee, employer er, WHERE ee.employer_id = er.id)。最后,请参阅***.com/a/25694525/14731“[..] 只读行可能 [...] 导致死锁” 即使SERIALIZABLE 事务也可能死锁。但是相同的语句不能对自身死锁,除非您的意思是在不同的事务中单独并同时执行的相同语句。因此,按固定顺序获取锁的全部目的是防止任何其他事务通过第一个障碍,直到前一个事务完成它的工作。例如。锁定 t1,锁定 t2,在 t1 和 t2 上执行工作。第二个事务执行相同的块,在第一个事务完成之前无法获取 t1 锁(第一步)。

以上是关于如何避免数据库死锁?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免这两个 SQL 语句之间出现死锁?

「操作系统」深入理解死锁(什么是死锁?死锁形成条件?如何避免死锁?如何排查死锁?)

如何避免死锁

java如何避免死锁

mysql 开发进阶篇系列 14 锁问题(避免死锁,死锁查看分析)

如何避免死锁