提到sql server,想必最讓人頭疼的當屬鎖機制了。在默認的read committed隔離模式下,連最基本的select操作都要申請各種粒度的鎖,而且在讀取數據過程中會不斷有鎖升級、轉化。在非未提交讀的隔離級別中,一個select操作會對每一條讀到的記錄或鍵值加S鎖(何時釋放還要視記錄是否返回以及隔離級別而定),對每一條用到的Index上的鍵值加S鎖,對讀過的每個page和table上加IS鎖...update、insert、delete操作申請鎖的量和復雜度就更大了。
死鎖和阻塞都是sql server要實現事務隔離的產物。有時候在同一個表上的事務隔離,並發度高一點會發生死鎖;並發度低一點發生的是阻塞。所以死鎖的問題定位和解決與阻塞有想通的地方,解決死鎖最關鍵的就是要找到死鎖雙方或多方共同爭搶的資源是哪個。下面分享一個最近碰到的真實生產環境上的案例,解析死鎖抓取以及解決過程。
某外資物流公司
操作系統:Windows Server 2012 Enterprise x64
數據庫 :SQL Server 2014 Enterprise X64
數據量 :300GB左右日常事務並發量比較高
現狀 :由於一個業務sp的大量並行運行導致死鎖,死鎖發生一方作為犧牲資源后回滾過程很漫長導致重要業務表被鎖,業務中斷
解決排查過程:
首先必須找到死鎖資源:
1)通過SQL Server Profiler新建一個trace,事件選擇可以精簡點便於我們觀察死鎖,選擇“Locks”事件
下的Lock:Deadlock和Deadlock graph即可,trace文件大小設置為100M上限以便分析
下的Lock:Deadlock和Deadlock graph即可,trace文件大小設置為100M上限以便分析
2)一段時間后停止抓取,很直觀看到死鎖一直出現,且點開所有deadlock graph得到死鎖圖形分析,死鎖都是發生在同一資源上:
3)為了查看死鎖信息,數據庫引擎提供了監視工具:跟蹤標識(1222)。打開這個跟蹤開關,所有獲取的死鎖信息會寫到SQL Server的錯誤日志中供我們進一步分析。這一步打開這個開關,在SSMS中運行
DBCC TRACEON(1222,-1);
4)從trace的死鎖圖形看死鎖發生很頻繁,為了不讓日志增長過大,過2至3分鍾后將開關關掉。在SSMS中運行
DBCC TRACEOFF(1222,-1),這一步很重要;
5)打開SQL Server errorlog,找到死鎖輸出信息,這個輸出內容很豐富而且比較復雜,這里只把我們所需的幾個重要點挑出來

死鎖信息始於 deadlock-list關鍵字(倒着看),deadlock victim顯示死鎖的犧牲方,process id顯示進程id號,由於截圖沒那么齊全,還包含很多死鎖信息,比如可以查看進程spid號,事務隔離級別,當前正進行的批處理操作,當前正在運行的語句,申請中的資源等等。
通過對錯誤日志的分析得到死鎖批處理和死鎖語句:exec usp_obal_import_so,查到死鎖語句:delete from t_po_detail where po_number in(select po_number from t_so_po where so_number=@v_vchSOID and whid=@v_vchWHID) and
whid=@v_vchWHID,這是usp_obal_import_so中的一段語句,鎖資源:表tbl_po_detail_generic(用戶腳本定時執行獲得) ,但是這個sp的執行根本不會操作tbl_po_detail_generic ,是不是哪里出問題了呢?
6)我們可以在SSMS中看看這條語句的執行計划,運行語句之前在SSMS中運行
set statistics profile on或者在“查詢”子菜單中選擇“包括實際的執行計划”,我們用第二種更直觀,如下
很明顯在執行計划中可以看到tbl_po_detail_generic有個全表掃描操作,再與t_po_detail表做hash連接。全表掃描導致每次語句執行會獲取該表的表鎖,深究原因發現tbl_po_detail_generic的外鍵約束導致每次刪除t_po_detail數據會操作tbl_po_detail_generic表。

很明顯在執行計划中可以看到tbl_po_detail_generic有個全表掃描操作,再與t_po_detail表做hash連接。全表掃描導致每次語句執行會獲取該表的表鎖,深究原因發現tbl_po_detail_generic的外鍵約束導致每次刪除t_po_detail數據會操作tbl_po_detail_generic表。
7)到這里剖析死鎖工作基本結束,后面解決方法有兩種:一是根據執行計划在tbl_po_detail_generic建立適當索引避免表掃描;二是如果業務邏輯許可,刪掉外鍵約束。
總結:
要真正做到從源頭上降低死鎖發生幾率,還是要從程式本身做好。如果不能去修改程式,可以考慮從另外幾個方面消除死鎖:
1 調整索引來調整執行計划,減少鎖的申請數目;
2 使用'nolock'參數,讓SELECT語句不要申請S鎖,減少鎖申請數目
3 升級鎖粒度,將死鎖轉化成阻塞問題
4 使用快照隔離級別SNAPSHOT LEVEL