SQL Server索引更新死锁


13

我有2个查询,当同时运行时会导致死锁。

查询1-更新索引(index1)中包含的列:

update table1 set column1 = value1 where id = @Id

在table1上获取X-Lock,然后在index1上尝试X-Lock。

查询2:

select columnx, columny, etc from table1 where {some condition}

在index1上获取S-Lock,然后在table1上尝试S-Lock。

有没有办法在保持相同查询的同时防止死锁?例如,我是否可以在更新之前以某种方式对更新事务中的索引进行X锁定,以确保表和索引访问的顺序相同-这样可以防止死锁?

隔离级别为“已提交读”。已为索引启用行锁和页锁。同一条记录可能同时参与这两个查询-从死锁图中我看不出来,因为它没有显示参数。

死锁图

Answers:


11

有没有办法在保持相同查询的同时防止死锁?

死锁图显示此特定死锁是与书签查找(在这种情况下为RID查找)相关联的转换死锁:

死锁图

正如问题所指出的那样,由于查询可能会以不同的顺序对相同资源获得不兼容的锁,因此出现了一般的死锁风险。由于SELECT需要进行RID查找,因此查询需要在表之前访问索引,而UPDATE查询首先修改表,然后修改索引。

消除僵局需要除去僵局成分之一。以下是主要选项:

  1. 通过进行非聚集索引覆盖来避免RID查找。在您的情况下,这可能不切实际,因为SELECT查询返回26列。
  2. 通过创建聚簇索引来避免RID查找。这将涉及在列上创建聚簇索引Proposal。值得考虑的是,尽管此列的类型似乎是,但uniqueidentifier取决于更广泛的问题,对于聚集索引而言,它可能是一个好选择,也可能不是。
  3. 通过启用READ_COMMITTED_SNAPSHOTSNAPSHOT数据库选项,避免在读取时获取共享锁。这将需要仔细测试,尤其是对于任何设计的阻止行为。触发代码还需要进行测试以确保逻辑正确执行。
  4. 通过使用查询的READ UNCOMMITTED隔离级别,避免在读取时获取共享锁SELECT。所有常规注意事项均适用。
  5. 通过使用排他的应用程序锁,避免同时执行有问题的两个查询(请参见sp_getapplock)。
  6. 使用表锁提示来避免并发。这比选项5更大,因为它可能会影响其他查询,而不仅仅是问题中标识的两个。

我可以在更新之前以某种方式对更新事务中的索引进行X锁定以确保表和索引访问的顺序相同

你可以试试这个,通过在显式事务包装更新,并执行SELECTXLOCK在更新之前的非聚集索引值提示。这依赖于您确定非聚集索引中的当前值是什么,确定正确的执行计划,并正确预期采取此额外锁定的所有副作用。它也依赖于锁定引擎不够聪明,以至于在判断为冗余时避免采取锁定措施。

简而言之,尽管这在原则上是可行的,但我不建议这样做。错过某些东西或以创新的方式超越自己太容易了。如果您确实必须避免这些死锁(而不仅仅是检测到它们并重试),我鼓励您看一下上面列出的更通用的解决方案。


通过进一步研究该问题,我认为最好保持不变。这是我最初意识到的一个更常见的问题。
Dale K'1

1

我偶尔也会遇到类似的问题,这是我采用的方法。

  1. 添加set deadlock priority low;到选择。当发生死锁时,这将使该查询成为死锁的受害者。
  2. 在应用程序中,如果由于死锁(或超时)而导致选择失败,则在等待/休眠一小段时间后,应用程序中的安装程序重试逻辑会自动重试选择,以使阻塞查询得以完成。

注意:如果您select是显式多语句事务的一部分,则需要确保重试整个事务,而不仅仅是重试失败的语句,否则您将获得一些意外结果。如果这是单个select语句x,则没问题,但如果它是n事务中的语句,则只需确保重试n期间重试所有语句即可。


谢谢-默认情况下,查询自动成为死锁的受害者。是的,我们已经有了一个强大的重试机制。
Dale K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.