8

我有執行的TableB一個聯接到一個TableA存儲過程:SQL Server死鎖修復:強制連接命令或自動重試?

SELECT <--- Nested <--- TableA 
      Loop <-- 
         | 
         ---TableB 

與此同時,在交易中,行插入TableA,然後進入TableB

這種情況偶爾會造成死鎖,因爲存儲過程中選擇待價而沽行從表B,而插入添加行表A,然後每個人都希望對方讓對方表的走:

INSERT  SELECT 
========= ======== 
Lock A  Lock B 
Insert A Select B 
Want B  Want A 
....deadlock... 

邏輯要求INSERT到第一行添加到一個,然後,而我個人不喜歡在SQL Server執行其加入的順序 - 只要它加入。

修復死鎖的常見建議是確保每個人都以相同的順序訪問資源。但在這種情況下,SQL Server的優化器告訴我,相反的順序是「更好」。我可以強制另一個連接順序,並有一個更糟糕的執行查詢。

但我應該嗎?

我應該重寫優化器,現在和永遠,我想要它使用的連接順序?

或者我應該只是捕獲錯誤本地錯誤1205,並重新提交select語句?

問題是,當我重寫優化程序併爲它做非最優化的事情時,查詢可能執行的程度會有多糟糕。問題是:自動重試更好,而不是運行更糟糕的查詢?

回答

9

自動重試死鎖會更好嗎?原因是你可能會修復這個死鎖,只能在以後再打一個。如果表的大小發生更改,服務器硬件規格發生更改以及服務器上的負載發生更改,則SQL發行版之間的行爲可能會發生變化。如果死鎖頻繁發生,你應該採取積極措施消除它(索引通常是答案),但對於罕見的死鎖(比如每10分鐘左右),應用程序的重試可以掩蓋死鎖。您可以重試讀取寫入,因爲寫入當然包含在正確的開始事務/提交事務中,以使所有寫入操作保持原子狀態,因此無需重試即可解決問題。

要考慮的另一個途徑是開啓read committed snapshot。如果啓用此選項,SELECT將不會執行任何鎖定,但會產生一致的讀取。

+0

Remus, 重試讀取非常合理,但在死鎖後自動重試寫入會導致丟失更新。當你寫入併成爲死鎖受害者時,你接觸到的數據可能會被其他人修改。在這種情況下,我們不應該自動重寫。我們應該重新讀取可能修改的數據,並再次考慮是否要保存。說得通? – 2010-03-05 01:30:36

+1

@AlexKuznetsov:這很荒唐,如果一個事務被正確地寫入(即自動),那麼如何重試它可能導致丟失的更新?我給這個+1,這絕對是正確的答案。你無法阻止每一個死鎖,它只是帶有ACID語義的背景噪聲的一部分。 – Aaronaught 2010-03-05 01:43:51

+0

@Alex,@Aaro:你其實都是對的。通過「重試」,我的意思確實是「讀取當前狀態,應用更改,寫回新狀態」。對於自動化處理應用程序,這是一個非常容易實現的模式。但是,對於用戶交互式應用程序來說,這可能比較困難,通常適當的操作是通過重新讀取當前狀態並將其重新顯示給用戶來推回「寫入」,以便他/她可以確認應用的更改在新的狀態/背景下有意義,我認爲這就是Alex想到的。因此,正確的行動依情況而定。 – 2010-03-05 01:55:46

2

陷印和重新運行可以工作,但您確定SELECT始終是死鎖受害者嗎?如果插入是死鎖受害者,那麼您必須更加小心地重試。

在這種情況下,我認爲最簡單的解決方案是NOLOCK或READUNCOMMITTED(同樣的事情)你的選擇。人們對髒讀有理由擔心,但是我們已經在全國範圍內運行NOLOCK來實現更高的併發性,並且從來沒有出現問題。

我也會對鎖語義做更多的研究。例如,我相信如果您將事務隔離級別設置爲快照(需要2005或更高版本),您的問題就會消失。

+0

SQL Server以最少的持有資源回滾事務。 「插入」是一系列可能有一打插入的事務。 select是一個單獨的select(包裝在存儲過程中) – 2010-03-05 00:17:04

+0

@Ian Boyd:單個'SELECT'語句不能創建死鎖情況。您需要至少有兩個多語句事務。他們都不需要是DML,但他們都必須等待彼此資源的鎖定,這意味着他們都必須至少使用兩種資源。如果它實際上只是一個'SELECT'語句,而不是被任何更大的事務包裹,那麼它可能不是真正的死鎖,它可能只是I/O系統努力跟上或其他奇怪的服務器問題。 – Aaronaught 2010-03-05 02:51:54

+0

@Aaronaught。一個select **可以**導致另一個進程發生死鎖(http://blogs.msdn.com/bartd/archive/2006/09/25/770928.aspx) – 2010-03-05 13:10:37

5

爲避免死鎖,最常見的建議之一是「以相同順序獲取鎖」或「以相同順序訪問對象」。顯然這很有道理,但它總是可行嗎?它總是可能的嗎?當我不能聽從這個建議時,我一直遇到案件。

如果我在一個父表和一個或多個子表中存儲一個對象,我根本無法遵循這個建議。插入時,我需要先插入我的父行。刪除時,我必須以相反的順序進行。

如果我使用的命令觸及一個表中的多個表或多個行,那麼通常我無法控制獲取哪個順序鎖(假設我沒有使用提示)。

因此,在很多情況下試圖以相同的順序獲取鎖並不能阻止所有的死鎖。所以,無論如何,我們需要某種處理死鎖 - 我們不能認爲我們可以將它們全部消除。 除非我們使用Service Broker或sp_getapplock序列化所有訪問。

當我們在死鎖後重試時,我們很可能會覆蓋其他進程的更改。我們需要知道,很可能有其他人修改了我們打算修改的數據。特別是如果所有閱讀器都在快照隔離下運行,那麼讀者不會參與死鎖,這意味着參與死鎖的所有參與者都是編寫者,修改或試圖修改相同的數據。如果我們只是捕獲異常並自動重試,我們可以覆蓋其他人的更改。

這被稱爲丟失更新,這通常是錯誤的。通常情況下,死鎖之後要做的正確事情是在更高的層次上重試 - 重新選擇數據並決定是否以原始保存決策的相同方式進行保存。

例如,如果用戶按下Save按鈕並選擇保存事務作爲死鎖受害者,那麼在死鎖之後重新顯示屏幕上的數據可能是個好主意。

+0

+1在交互式應用程序中,這是真實的:如果寫入已死鎖,則正在更新的狀態很可能已更改,因爲這正是發生死鎖的資源。我的回答受到我在隊列處理方面的背景的影響,其中「更高級別」包含在回滾事務中。 – 2010-03-05 03:46:07

+0

@AlexKuznetsov:我不太同意重試更新的危險。如果用戶在200毫秒後碰巧點擊了按鈕,而不是更快,效果將是相同的。 – 2010-03-05 13:15:09

+0

如果您的應用程序已經設計爲支持樂觀併發,那麼將死​​鎖視爲衝突是合理的。如果應用程序無論如何都會覆蓋更改,那麼您可能只是重試更新。 – Aaronaught 2010-03-05 14:41:02