SELECT JOIN语句与SQL Server引起的死锁

问题描述:

当执行带有两个表的JOIN的SELECT语句时,SQL Server似乎是 单独锁定了两个语句的表。例如,通过像 这样的查询:SELECT JOIN语句与SQL Server引起的死锁

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 

我发现锁的顺序取决于WHERE条件。查询优化器会尝试生成一个执行计划,根据需要只读取尽可能多的行数 。因此,如果WHERE条件包含一列表1 它将首先从table1中获取结果行,然后从table2中获取相应的 行。如果该列来自table2,它将以 一轮的方式执行此操作。更复杂的条件或使用索引可能会影响查询优化器的决策。

如果语句读取的数据应该随后在具有UPDATE语句的事务 中更新,则不保证UPDATE 语句的顺序与用于从2个表中读取数据的顺序相匹配。 如果在事务更新 表时另一个事务尝试读取数据,则在UPDATE语句之间在 之间执行SELECT语句时,可能会导致死锁,因为SELECT不能在第一个表上获得 的锁定,也不能在UPDATE获得第二张桌子上的锁。对于 例如:

T1: SELECT ... FROM ... JOIN ... 
T1: UPDATE table1 SET ... WHERE id = ? 
T2: SELECT ... FROM ... JOIN ... (locks table2, then blocked by lock on table1) 
T1: UPDATE table2 SET ... WHERE id = ? 

两个表代表一个类型的层次结构,并总是加载到一起。所以它 有意义使用带JOIN的SELECT加载对象。单独加载两个表 不会使查询优化器有机会找到最佳的执行计划 。但由于UPDATE语句只能在 时间更新一个表,所以当一个对象被加载而另一个事务更新了对象 时,这可能导致死锁。对属于不同类型的 类型层次结构的对象的属性进行更新时,对象的更新通常会在两个表上导致更新。

我试图向SELECT语句添加锁定提示,但这并不是 更改问题。当 两个语句试图锁定表并且一个SELECT语句以其他语句的相反顺序获取锁 时,它只会导致SELECT语句中的死锁。也许可以通过 加载数据进行更新,并始终使用相同的语句强制锁定为 。这将防止两个事务之间的死锁,即 想要更新数据,但不会阻止只有读取数据为 的事务需要具有不同的WHERE条件的事务。

这是迄今为止唯一的一轮工作,似乎是读取可能根本没有锁 。对于SQL Server 2005,可以使用SNAPSHOT ISOLATION完成。 SQL Server 2000的唯一方法是使用READ UNCOMMITED隔离级别 级别。

我想知道是否有另一种可能性来防止SQL Server 导致这些死锁?

+0

快照隔离级别? – 2010-09-14 20:58:41

+0

如果这是一个问题,答案是否定的。它发生在所有的隔离级别,除了READ UNCOMMITTED,我没有测试,因为我不希望这些事务可以读取一半更新的数据。 – Reboot 2010-09-14 21:20:12

当读者不阻止作者时,在快照隔离下不会发生这种情况。除此之外,没有办法阻止这种事情发生。我写了很多摄制脚本的位置:Reproducing deadlocks involving only one table

编辑:

我没有访问SQL 2000,但我会尝试使用sp_getapplock能够连续访问的对象,这样的阅读和修改从不同时运行。如果你不能使用sp_getapplock,推出你自己的互斥锁。

+0

+1为僵局研究 – 2010-09-14 21:38:26

+0

答案帮助我找到了与SQL Server 2005或更新的分离。但是该软件仍然与SQL Server 2000一起使用,我可以为这两个版本实现不同的解决方案,因此可以使用适用于所有版本或适用于SQL Server 2000的不同解决方案的解决方案。 – Reboot 2010-09-17 08:28:18

我正面临同样的问题。使用查询提示FORCE ORDER将解决此问题。缺点是你无法利用查询优化器对查询的最佳计划,但这会防止死锁。如果你有一个查询FROM table1 LEFT JOIN table2,而你的WHERE子句只包含来自table2的列,那么执行计划通常会首先从table2中选择行,然后查找来自table1的行。对于table2中的小结果集,只需要获取table1中的几行。使用FORCE ORDER,首先必须获取table1中的所有行,因为它没有WHERE子句,那么将连接来自table2的行并使用WHERE子句过滤结果。因此降低了性能。

但是,如果你知道这不是这种情况,请使用这个。您可能想要手动优化查询。

语法

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 
OPTION (FORCE ORDER) 
+0

其实我写了关于性能的评论,似乎堆栈溢出已经搞乱了你的答案和我的评论,我已经想知道为什么它消失了。问题不仅在于FORCE ORDER的性能,它也是锁定。如果您使用FORCE ORDER并导致执行计划读取table1中的所有行,它还会在其上放置共享锁。所以基本上每个只使用table2中的列的查询都会锁定整个table1,使得更新无法进行。这不是一个需要工作的查询,SELECT必须与任何WHERE子句一起工作。 – Reboot 2011-04-21 23:13:41

+0

所以你不想锁定table1,而能够加入table2?在我的情况下,我有插入发生在table1,然后在table2。查询优化器在连接的情况下选择反向顺序。所以僵局。增加FORCE ORDER解决了因此没有死锁。请注意,我仍然希望在table1上读取锁定。插入可以等到选择完成。但不会有任何僵局。 – Ankush 2011-04-22 07:00:25

+0

对table1没有锁定,但FORCE ORDER锁定表中的所有行,而不是仅需要与来自table2的行连接的行。为所有行分配锁也可能成为问题,因为它是一个资源进程必须分配的。如果SQL Server为锁运行我们的内存,则进程可能会在内存分配时出现死锁。 – Reboot 2011-04-23 11:50:57

解决这个问题的另一种方法是分裂的选择... ...从加入到多个SELECT语句。将隔离级别设置为读取已提交。使用表变量来管理select中要加入其他数据的数据。使用distinct可以将插入过滤到这些表变量中。

所以,如果我有两个表A,B我插入/更新到A然后B在哪里作为SQL的查询优化器喜欢先读B和A.我将单选择分为2选择。首先我将读到B.然后将这些数据传递给下一条选择语句,其内容为A.

这里不会发生死锁,因为表B上的读锁将在第一条语句完成时立即释放。

PS我已经遇到了这个问题,这工作非常好。比我的部队命令答案好得多。