使用 TRY/CATCH 語句解決 SQL Server 2005 死鎖

發佈日期: 12/26/2005 | 更新日期: 12/26/2005

Ron Talmage

對於今天的 RDBMS 體系結構而言,死鎖難以避免在高容量的 OLTP 環境中更是極爲普遍。正是由於 .NET 的公共語言運行庫 (CLR) 的出現,SQL Server 2005 才得以爲開發人員提供一種新的錯誤處理方法。在本月專欄中,Ron Talmage 爲您介紹如何使用 TRY/CATCH 語句來解決一個死鎖問題。

*

一個示例死鎖


讓我們從這樣一個示例開始說起,它在 SQL Server 2000 和 2005 中都能引起死鎖。在本文中,我使用 SQL Server 2005 的最新 CTP(社區技術預覽,Community Technology Preview)版本,SQL Server 2005 Beta 2(7 月發佈)也同樣適用。如果您沒有 Beta 2 或最新的 CTP 版本,請下載 SQL Server 2005 Express 的最新版本,用它來進行試驗。

可能發生的死鎖情況有很多,[參閱http://msdn.microsoft.com/library/default.asp?url=/library/en-us/acdata/ac_8_con_7a_3xrf.asp以及死鎖文章樹中的後續文章。編者 ],但最有趣、最微妙的是那些關於閱讀器和編寫器互相阻塞的死鎖。以下代碼在 pubs 數據庫中就產生了這樣一個死鎖。(您可以在 SQL Server 2000 的兩個 Query Analyzer 窗口中或 SQL Server 2005 的兩個 Management Studio queries 中並列運行這段代碼。)在其中一個窗口中的代碼正文前面添加下列語句:

-- Window 1 header
DECLARE @au_id varchar(11), @au_lname varchar(40)
SELECT @au_id = '111-11-1111', @au_lname = 'test1'

在第二個窗口中添加下列語句,進行第二次連接:

-- Window 2 header
DECLARE @au_id varchar(11), @au_lname varchar(40)
SELECT @au_id = '111-11-1112', @au_lname = 'test2'

在兩個窗口中都使用下列語句作爲代碼正文:

-- Body for both connections:
BEGIN TRANSACTION
INSERT Authors VALUES 
  (@au_id, @au_lname, '', '', '', '', '', '11111', 0)
WAITFOR DELAY '00:00:05'
SELECT *
  FROM authors
  WHERE au_lname LIKE 'Test%'
COMMIT

在第三個窗口中運行下列語句,確保 authors 表格中沒有任何包含以下 id 的數據:

DELETE FROM authors WHERE au_id = '111-11-1111'
DELETE FROM authors WHERE au_id = '111-11-1112'

在 5 秒鐘內同時執行窗口 1 和 窗口 2。因爲每個窗口都要等待至少 5 秒鐘的時間才能發出 SELECT 語句,所有每個連接都將完成 INSERT 操作,這樣就保證了兩個窗口中的 INSERT 操作在各自的 SELECT 語句發佈前就已經完成了。每個窗口中的 SELECT 語句都嘗試讀取 authors 表格中的所有數據,查找 au_lname 字段值中類似“Test%”格式的數據。因此,兩個窗口中的 SELECT 語句都將嘗試讀取各自連接中的插入數據 — 也讀取對方連接中的插入數據。

READ COMMITTED 隔離級別通過發佈共享鎖確保 SELECT 語句永遠不讀取未提交的數據。對於同一個資源,共享鎖與排它鎖互不兼容,請求者在發佈共享鎖之前必須等待排它鎖釋放。每個連接對於插入的數據都設置了排它鎖,因此嘗試讀取對方插入數據的 SELECT 語句將試圖解除插入數據的共享鎖,但它會被阻塞。兩個連接將互相阻塞,從而形成一個死鎖。SQL Server 的鎖定管理器檢測到死鎖時,將中止其中的一個批處理,回滾它的事務,釋放它的阻塞鎖,以便其他事務能夠完成。作爲死鎖犧牲品的事務將回滾,其他事務則將成功完成。

如何使用 TRY/CATCH 語句避免死鎖


現在,讓我們來使用 TRY/CATCH 語句修改代碼正文。(對於本示例,需要以 SQL Server 2005 版本運行代碼。)使用 TRY/CATCH 時,操作代碼和錯誤處理代碼是分開的。您應該將執行一個操作的代碼放在 TRY 語句塊中,將錯誤處理代碼放在 CATCH 語句塊中。如果 TRY 語句塊中的代碼執行失敗,代碼執行將跳到 CATCH 語句塊。(除了那些防礙整個批處理運行的錯誤(如,丟失對象),該方法幾乎適用於所有的錯誤。)

以下示例使用 TRY/CATCH 語句對前面使用的代碼進行了改寫。代碼標題相同,但是代碼正文不同:

BEGIN TRANSACTION
BEGIN TRY
  INSERT Authors VALUES 
  (@au_id, @au_lname, '', '', '', '', '', '11111', 0)
  WAITFOR DELAY '00:00:05'
  SELECT COUNT(*)  FROM Authors 
  COMMIT
END TRY
BEGIN CATCH
  SELECT ERROR_NUMBER() AS ErrorNumber
  ROLLBACK
END CATCH;
SELECT @@TRANCOUNT AS '@@Trancount'

現在,在連接到 SQL Server 2005 的並列窗口中運行這些代碼,在此之前您需要確認已經刪除了 authors 表格中任何可能阻止插入操作的數據;或者,您可以使用前置 DELETE 語句。

兩個窗口返回的 @@TRANCOUNT 級別都爲 0,這表明仍然發生了死鎖,但 TRY/CATCH 語句捕獲了這次發生的死鎖。死鎖犧牲品的批處理沒有再次中止,可在它的輸出結果中看到錯誤:

ErrorNumber
-----------
1205

@@Trancount
-----------
0

您應該已經發現 TRY/CATCH 語句具有的威力了。因爲死鎖錯誤能夠爲 CATCH 語句塊所捕獲,所以批處理將不再中止,T-SQL 代碼也能繼續執行。對於死鎖犧牲品而言,死鎖錯誤 1205 將代碼放入 CATCH 語句塊 — 在這裏您可以使用新的錯誤處理函數瀏覽死鎖錯誤。前置代碼僅使用 ERROR_NUMBER() 函數取代 @@ERROR 變量,您也可以使用 ERROR_MESSAGE()、ERROR_PROCEDURE()、ERROR_SEVERITY() 和 ERROR_STATE()。這些函數的功能一目瞭然,它們提供的功能比我們以往使用的更多。

請注意,這個前置 CATCH 語句塊包含一個 ROLLBACK。這樣做的原因是,即使捕獲了死鎖錯誤,事務也不會回滾。事務仍然要失敗,但是,現在您有責任在 TRY/CATCH 語句中回滾事務。那麼,區別在哪裏?儘管您不能使事務繼續進行,但是您能夠 重試事務!

在 TRY/CATCH 語句中進行重試


在 SQL Server 2000 的 T-SQL 中,錯誤 1205 令人沮喪之處是它提供的建議:“Rerun the transaction.”問題是,至少在 SQL Server 2000 的 T-SQL 中,您不能做到這一點。但是,由於 SQL Server 2005 的 TRY/CATCH 爲我們提供了捕獲死鎖錯誤的方法,現在,重試事務是可能實現的。

以下代碼正文說明了一種執行重試操作的方法。這段代碼仍然使用與前面相同的標題:

DECLARE @Tries tinyint
SET @Tries = 1
WHILE @Tries <= 3
BEGIN
  BEGIN TRANSACTION
  BEGIN TRY
    INSERT Authors VALUES 
      (@au_id, @au_lname, '', '', '', '', '', 
'11111', 0)
    WAITFOR DELAY '00:00:05'
    SELECT * FROM authors WHERE au_lname LIKE 'Test%'
    COMMIT
    BREAK
  END TRY
  BEGIN CATCH
    SELECT ERROR_NUMBER() AS ErrorNumber
    ROLLBACK
    SET @Tries = @Tries + 1
    CONTINUE
  END CATCH;
END

這段代碼的功能是通過一個 WHILE 循環添加一個重試操作。我將重試次數設置爲 3,重試次數是可以配置的。至少我們現在有了一種在 T-SQL 內重試一個死鎖犧牲品代碼的方法 — 這是我們過去一直無法做到的。

但是,需要注意整個事務是在 WHILE 循環內進行的 — 而不是在循環外部。因此執行循環時,事務不僅在每個循環體內部開始,而且也在其中結束 — 不是 TRY 語句塊執行完畢,返回一個 COMMIT,就是 CATCH 語句塊執行,返回一個 ROLLBACK。如果 TRY 成功,TRY 語句塊將以一個 BREAK 語句結束,退出 WHILE 循環。否則,CATCH 語句塊將重試計數器加 1,以一個 CONTINUE 語句結束本次循環,重新執行下次 WHILE 循環。事實上,您有實現重試事務的代碼 — 就像錯誤 1205 告訴我們做的那樣。但現在,重試操作完全在 T-SQL 內部完成。

SQL Server 2005 也提供幫助解決死鎖問題的其他方法,例如 SNAPSHOT ISOLATION 級別和用於 READ COMMITTED 的新選項(稱爲 READ COMMITTED SNAPSHOT)。然而,這一事實 — 現在,通過 SQL Server 2005,您能夠對事務進行編碼並捕獲死鎖錯誤(並重試它們) — 已經意味着您擁有一個可任意支配、功能更加強大的工具。

下載:單擊下載按鈕來下載代碼 (505RON.SQL)

要查找有關 SQL Server Professsional 和 Pinnacle Publishing 的信息,請訪問它們的 Web 站點 http://www.pinpub.com/

注:這個網站不是 Microsoft Corporation 的 Web 站點。Microsoft 對該網站的內容不承擔責任。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章