作為DBA,可能經常會遇到有同事或者客戶反映經常發生死鎖,影響了系統的使用。此時,你需要盡快偵測和處理這類問題。
死鎖是當兩個或者以上的事務互相阻塞引起的。在這種情況下兩個事務會無限期地等待對方釋放資源以便操作。下面是死鎖的示意圖:

本文將使用SQLServer Profiler來跟蹤死鎖。
准備工作:
為了偵測死鎖,我們需要先模擬死鎖。本例將使用兩個不同的會話創建兩個事務。
步驟:
1、 打開SQLServer Profiler
2、 選擇【新建跟蹤】,連到實例。
3、 然后選擇【空白】模版:

4、 在【事件選擇】頁中,展開Locks事件,並選擇以下事件:
1、 Deadlock graph
2、 Lock:Deadlock
3、 Lock:Deadlock Chain

5、 然后打開TSQL事件,並選擇以下事件:
1、 SQL:StmtCompleted
2、 SQL:StmtStarting

6、 點擊【列篩選器】,在跟蹤屬性中,選擇數據庫名為需要偵測的數據庫,這里使用AdventureWorks。

7、 在【組織列】中,調整順序,如下:

8、 點擊運行。
9、 然后打開SQLServer,並打開兩個連接。
10、 在第一個窗口中輸入並執行下面腳本:
- USE AdventureWorks
- GO
- SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
- GO
- BEGIN TRANSACTION
- SELECT *
- FROM Sales.SalesOrderDetail
- WHERE SalesOrderDetailID = 121316
11、 然后在第二個窗口中輸入並執行下面腳本:
- USE AdventureWorks
- GO
- SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
- BEGIN TRANSACTION
- SELECT *
- FROM Sales.SalesOrderDetail
- WHERE SalesOrderDetailID = 121317
12、現在回到第一個窗體,並運行下面的腳本:
- UPDATE Sales.SalesOrderDetail
- SET OrderQty=2
- WHERE SalesOrderDetailID=121317
13、在第二個窗口輸入下面語句:
- UPDATE Sales.SalesOrderDetail
- SET OrderQty=2
- WHERE SalesOrderDetailID=121316
14、 然后在第二個窗口就會看到下面的消息:

15、切換到SQLServer Profiler,可以看到下面的截圖:

16、 點擊【Deadlock graph】時間,會顯示死鎖的圖像:

17、可以保存死鎖圖像,右鍵然后選擇導出事件數據,並另存為xdl文件:

下面是其XML格式:

分析:
在本文中,首先創建一個Profiler空白模版,然后選擇下面的事件進行監控:
1、 Deadlock graph
2、 Lock:Deadlock
3、 Lock:Deadlock Chain
4、 SQL:StmtCompleted
5、 SQL:StmtStarting
然后通過限定數據庫,來限制監控過得對象范圍。
在配置好之后,運行跟蹤,並在ssms中運行腳本。SQLServer會自動處理和偵測這種類型的死鎖。然后會在第二個窗體中收到1205的錯誤。
在SQLServer Profiler中,演示了如何收集死鎖事件,在跟蹤結果中可以看到兩個事務嘗試在一個擁有共享鎖的鍵上添加排它鎖。通過死鎖圖像,可以看到死鎖發生的細節。
為了避免或者最小化死鎖的發生,有一些建議可以參考:
1、 確保你的事務盡可能地小,這里指范圍。
2、 使用較低隔離級別的事務。
3、 對於可能的查詢,使用NOLOCK查詢提示。
4、 規范化數據庫設計。
5、 在需要的列上創建索引,以便是表不需要經常掃描,減少鎖問題的發生。
6、 控制數據庫對象訪問的順序是相同的順序。
ps:我用了這個例子,結果沒出現死鎖,用下面這個例子可以:
在一個窗口輸入以下語句:
BEGIN TRAN
USE TEST;
UPDATE T6 SET time3='2013-12-31 00:00:00.000'
WHERE time3='9999-09-09 00:00:00.000'
WAITFOR DELAY '0:0:15'
UPDATE T3 SET VALUE=5
WHERE ID=10
COMMIT TRAN
在另一個窗口輸入以下語句:
BEGIN TRAN
USE TEST;
UPDATE T3 SET VALUE=5
WHERE ID=10
WAITFOR DELAY '0:0:15'
UPDATE T6 SET time3='2013-12-31 00:00:00.000'
WHERE time3='9999-09-09 00:00:00.000'
COMMIT TRAN
在第一個窗口執行后,在15秒之內執行第二個窗口的查詢。
會發生死鎖。
事務的執行過程:
①:窗口1要更新T6的數據,需要向數據庫引擎申請T6的排他鎖。
②:窗口2要更新T3的數據,需要向數據庫引擎申請T3的排他鎖。
因為請求鎖的對象不同,所以它們可以同時得到想要的鎖。
③:在窗口2還沒有完成對T3更新前,窗口1完成了對T6的更新,然后想要更新T3的數據,此時需要請求T3的排他鎖。由於排他鎖與窗口2上面的T3上面的排他鎖不兼容,所以窗口1必須等待窗口2執行完事務,然后放在T3上面的排他鎖后,才能獲得對T3的排他鎖。
④:窗口2想要申請T6上的排他鎖同上面道理一樣。
雙方都等待對方釋放資源,才能繼續事務操作,從而形成死鎖。
例子中使用WAITFOR語句在執行兩條語句間休息5秒,可以增加死鎖的概率。
數據庫引擎會定期檢索死鎖,一旦發現問題,會選取其中一個事務作為犧牲品,終止事務的運行,從而釋放資源。
被犧牲的事務會顯示如下信息:
(1 行受影響)
消息 1205,級別 13,狀態 45,第 7 行
事務(進程 ID 55)與另一個進程被死鎖在 鎖 資源上,並且已被選作死鎖犧牲品。請重新運行該事務。
‘1行受影響’表示第一個UPDATE語句已經執行成功。但是由於事務沒有成功提交,所以回滾掉了。
本篇文章轉自:
