设计零SQL死锁-是否有任何编码模式?

时间:2020-03-06 14:30:46  来源:igfitidea点击:

在MS SQL Server 2005之上运行的.NET 2.0 Web应用程序上,我遇到了非常少见但令人讨厌的SQL死锁。过去,我们一直以非常经验的方式来处理SQL死锁,基本上是通过调整查询来使其工作。

但是,我发现这种方法非常不令人满意:既耗时又不可靠。我非常希望遵循确定性查询模式,该模式将通过设计确保永远不会遇到SQL死锁。

例如,在C多线程编程中,必须按照其字典顺序采用简单的设计规则(例如锁),以确保不会发生死锁。

是否有任何SQL编码模式可保证防死锁?

解决方案

如我们所说,始终以相同顺序访问表是避免死锁的一种非常好的方法。此外,请尽可能缩短交易时间。

另一个很酷的技巧是在可能的情况下将两个sql语句组合在一起。单个语句始终是事务性的。例如,使用" UPDATE ... SELECT"或者" INSERT ... SELECT",使用" @@ ERROR"和" @@ ROWCOUNT"代替" SELECT COUNT"或者" IF(EXISTS ...)"

最后,通过将查询重新发布可配置的次数来确保调用代码可以处理死锁。有时它只是发生,这是正常现象,应用程序必须能够处理它。

编写防死锁代码确实很困难。即使以相同的顺序访问表,也可能会出现死锁[1]。我在博客上写了一篇文章,阐述了一些方法,这些方法将避免和解决僵局。

如果要确保两个语句/事务永远不会死锁,则可以通过使用sp_lock系统存储过程观察每个语句消耗的锁来实现此目的。为此,我们必须非常快,或者使用带有保持锁提示的开放式事务。

笔记:

  • 一次需要多个锁的任何SELECT语句都可能因智能设计的事务而死锁,该事务以相反的顺序获取锁。

在一般情况下,零死锁基本上是一个代价非常高的问题,因为我们必须知道要为每个正在运行的事务(包括SELECTs)读取和修改的所有表/ obj。一般哲学称为有序严格两阶段锁定(不要与两阶段提交混淆)(http://en.wikipedia.org/wiki/Two_phase_locking;甚至2PL也不保证没有死锁)

很少有DBMS实际上执行严格的2PL,因为这样会导致大量性能下降(没有免费的午餐),而所有事务都在等待甚至简单的SELECT语句也要执行。

无论如何,如果我们对此真的很感兴趣,请看一下SQL Server中的" SET ISOLATION LEVEL"。我们可以根据需要进行调整。 http://en.wikipedia.org/wiki/Isolation_level

有关更多信息,请参见可序列化性上的Wikipedia:http://en.wikipedia.org/wiki/Serializability

就是说-一个很好的类比就像源代码修订版:尽早并经常签入。使事务小(在SQL语句中,对行进行修改)并且保持快速(挂钟时间有助于避免与其他事务发生冲突)。在一次交易中完成很多事情可能会很整洁-一般而言,我同意这一理念-但如果我们遇到很多僵局,则可以将交易分解为较小的僵局,然后检查它们的僵局。前进时在应用程序中的状态。 TRAN 1好吗?如果是,则发送TRAN 2 OK(是/否)?等

顺便说一句,在我多年担任DBA和开发人员(测量数千个并发用户的多用户DB应用程序)的过程中,我从未发现死锁是一个如此巨大的问题,因此我需要对其进行特殊的了解(或者更改隔离性)。意志意志坚强的水平,等等)。

如果我们对应用程序有足够的设计控制权,则将更新/插入限制在特定的存储过程中,并从应用程序使用的数据库角色中删除更新/插入特权(仅明确允许通过这些存储过程进行更新)。

将数据库连接隔离到应用程序中的特定类(每个连接都必须来自该类),并指定"仅查询"连接将隔离级别设置为"脏读" ...等效于每个联接(无锁) 。

这样,我们可以隔离可能导致锁定(针对特定存储过程)的活动,并将"简单读取"移出"锁定循环"。

快速答案是不,没有保证的技术。

我看不出如果有任何不平凡的吞吐量,如何将任何应用程序作为一般的死锁证明,作为一种设计原则。如果我们以相同的顺序抢先锁定了流程中可能需要的所有资源,即使我们最终并不需要它们,我们也可能会面临代价更高的问题,即第二个流程正在等待获取所需的第一个锁,并且可用性会受到影响。随着系统中资源数量的增加,即使是琐碎的进程也必须以相同的顺序锁定它们,以防止死锁。

解决SQL死锁问题(如大多数性能和可用性问题)的最佳方法是查看事件探查器中的工作负载并了解其行为。

除了一致的锁定获取顺序外,另一条路径是显式使用锁定和隔离提示,以减少浪费的时间/资源,这些资源/资源在读取期间无意识地获取了诸如共享意图之类的锁定。

不是我们问题的直接答案,而是值得深思的:

http://en.wikipedia.org/wiki/Dining_philosophers_problem

"吃饭的哲学家问题"是研究僵局问题的古老思想实验。阅读有关它的信息可能会找到针对特定情况的解决方案。

没有一个(令人惊讶地)提到的问题是,对于SQL Server而言,使用数据库查询工作负载的正确覆盖索引​​集可以消除许多锁定问题。为什么?因为它可以大大减少对表的聚集索引的书签查找次数(假设它不是堆),从而减少了争用和锁定。

在实践中,没有针对这个问题的通用解决方案。我们可以将并发推送到应用程序,但是这可能非常复杂,尤其是当我们需要与在单独的内存空间中运行的其他程序进行协调时。

减少死锁机会的一般答案:

  • 基本查询优化(适当使用索引)避免热点设计,使事务保持最短时间...等。
  • 可能的话,请设置合理的查询超时时间,以使如果发生死锁,则在超时期限到期后会自动清除。
  • MSSQL中的死锁通常是由于其默认的读取并发模型所致,因此不要依赖它非常重要-在所有设计中均采用Oracle风格的MVCC。使用快照隔离,或者在可能的情况下使用"读取未提交"隔离级别。