2010-09-14 56 views
7

當執行帶有兩個表的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

回答

5

當讀者不阻止作者時,在快照隔離下不會發生這種情況。除此之外,沒有辦法阻止這種事情發生。我寫了很多攝製腳本的位置: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

-1

我正面臨同樣的問題。使用查詢提示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

0

解決這個問題的另一種方法是分裂的選擇... ...從加入到多個SELECT語句。將隔離級別設置爲讀取已提交。使用表變量來管理select中要加入其他數據的數據。使用distinct可以將插入過濾到這些表變量中。

所以,如果我有兩個表A,B我插入/更新到A然後B在哪裏作爲SQL的查詢優化器喜歡先讀B和A.我將單選擇分爲2選擇。首先我將讀到B.然後將這些數據傳遞給下一條選擇語句,其內容爲A.

這裏不會發生死鎖,因爲表B上的讀鎖將在第一條語句完成時立即釋放。

PS我已經遇到了這個問題,這工作非常好。比我的部隊命令答案好得多。