深入淺出SQL Server中的死鎖 【轉】

来源:http://www.cnblogs.com/shouce/archive/2016/05/30/5541091.html
-Advertisement-
Play Games

簡介 死鎖的本質是一種僵持狀態,是多個主體對於資源的爭用而導致的。理解死鎖首先需要對死鎖所涉及的相關觀念有一個理解。 一些基礎知識 要理解SQL Server中的死鎖,更好的方式是通過類比從更大的面理解死鎖。比如說一個經典的例子就是汽車(主體)對於道路(資源)的徵用,如圖1所示。 圖1.對於死鎖的直 ...


簡介

    死鎖的本質是一種僵持狀態,是多個主體對於資源的爭用而導致的。理解死鎖首先需要對死鎖所涉及的相關觀念有一個理解。

 

一些基礎知識

    要理解SQL Server中的死鎖,更好的方式是通過類比從更大的面理解死鎖。比如說一個經典的例子就是汽車(主體)對於道路(資源)的徵用,如圖1所示。

    Traffic

     圖1.對於死鎖的直觀理解

 

    在圖1的例子中,每隊汽車都占有一條道路,但都需要另外一隊汽車所占有的另一條道路,因此互相阻塞,誰都無法前行,因此造成了死鎖。由這個簡單的例子可以看出,發生死鎖需要四個必要條件,如下:

1)互斥條件:

    主體對於資源是獨占的,圖1中每條汽車道只能跑一隊汽車,不能跑第二隊。

 

2)請求和等待條件:

     指主體已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它主體占有,此時請求主體阻塞,但又對自己已獲得的其它資源保持不放。在圖1中,每隊汽車已經占有了一條車道,又想獲得另一條由其它車隊占有的車道,造成阻塞。

 

3)不剝奪條件

    指的是主體已經獲得的資源在完成其目標之前不能被釋放。在圖1中,目標指的是汽車可以通過車道,不剝奪指的是在完成這個目標之前,車隊並不會讓出其已占的車道。

 

4)環路等待條件

    指在發生死鎖時,必然存在一個主體——資源的環形鏈,即主體集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。在圖1中可以看出,四條車道和四隊汽車正好符號環路等待的條件,車隊1希望獲得車隊2占有的車道,車隊2希望獲得車隊3占有的車道,車隊3希望獲得車隊4占有的車道,車隊4反過來又希望獲得車隊1占有的車道,形成一個環路。

 

死鎖在進程中的定義

    下麵讓我們再縮小死鎖的範圍,回到電腦的世界。在電腦中,主體的這個抽象的詞被更具體的進程所替代,而資源縮小到電腦所使用的資源。在電腦中,死鎖是由阻塞所引起。因此在開始之間,我想簡單介紹一下進程的幾種狀態,如果有興趣,也可以參看我之前的一篇文章:http://www.cnblogs.com/CareySon/archive/2012/05/04/ProcessAndThread.html.

    簡單來說,進程是組織資源的最小單位,多道操作系統中允許併發進行,每一道進程都像圖1所示的汽車那樣,需要前進,在前進的過程中,需要各種資源以及CPU,圖2是不考慮進行創建銷毀等狀態,簡單概述進程的幾種狀態。

    1

    圖2.進程的幾種狀態

 

    很多資源是可以共用的,比如記憶體。但對於印表機等資源來說就需要獨占。圖2中的幾種狀態簡單理解是,當進程沒有所需的資源時,比如說等待IO,等待印表機,這時是阻塞狀態。而當進程獲得了這些資源時,就可以變為就緒狀態,在就緒狀態的進程再獲得CPU時,就變為執行狀態。而執行的過程中,CPU被剝奪了就繼續變為就緒狀態,或是當需要其它資源時,就會繼續變為阻塞狀態。以此往複。

    在操作系統中,有些資源可以是不可剝奪資源,比如印表機,當印表機被一個進程占用時,另一個進程就會被阻塞。還有一類資源是要重點強調的,這類資源是臨時性資源,比如進程產生的信號量,消息,緩衝區內的消息,多個進程或線程訪問這類資源時更容易引起死鎖。在SQL Server中產生的死鎖其實就是由這類資源所造成的。

    當兩個或多個進程既然有了當前的資源,又需要額外的資源時,滿足了上面所述死鎖的四個條件時,就會產生死鎖。

 

死鎖在SQL Server中的定義

     在SQL Server中,阻塞更多的是產生於實現併發之間的隔離性。為了使得併發連接所做的操作之間的影響到達某一期望值而對資源人為的進行加鎖(鎖本質其實可以看作是一個標誌位)。當一個連接對特定的資源進行操作時,另一個連接同時對同樣的資源進行操作就會被阻塞(當然了,這和鎖之間的相容性有關,關於鎖更深入的討論超出了本文的範圍,關於這部分內容可以看我的另一篇文章:T-SQL查詢進階—理解SQL Server中的鎖),阻塞是死鎖產生的必要條件。

    下麵,我們通過一個簡單的例子來看死鎖。

     首先,要出現死鎖,一定要滿足前面提到死鎖出現的四個必要條件,圖3中可以清楚的看到這兩個連接(SPID52和SPID55)是如何滿足這四個條件的。

    3

    圖3.一個死鎖示例

   

Lock Monitor

    圖3中死鎖後可以看到,SQL Server並不會讓死鎖僵持下去,而是通過一個叫Lock Monitor的線程定期進行檢測(預設是5秒)。當發現死鎖後,會剝奪其中一個SPID占有的資源,好讓另一個SPID執行下去,具體剝奪哪一個SPID基於如下兩個因素:

    1.死鎖的優先順序。

    2.在死鎖優先順序相同的情況下,根據開銷,開銷小的事務將會被剝奪

 

    下麵,還是根據圖3中的例子,我們設置死鎖優先順序,使得左邊的事務被剝奪回滾,如圖4所示。

    4

    圖4.設置死鎖優先順序後,優先順序低的SPID被剝奪

 

SQL Server中死鎖的檢測

    首先要理解,在多併發的環境中,死鎖是不可避免的,只能儘量的通過合理的資料庫設計,良好的索引,適當的查詢語句以及隔離等級來儘量的減少。因此,檢測死鎖的目的是知道哪裡可能會產生死鎖,通過對檢測到的死鎖進行分析後,儘量的優化查詢/索引/隔離等級來降低死鎖發生的可能性。

    查看死鎖有兩種方式,一種是通過服務端的Trace來做,另一種是通過SQL Profiler,首先讓我們來看通過Trace來抓死鎖。

 

通過Trace來看死鎖

    當死鎖發生後,通過服務端的Trace就可以將死鎖信息傳到日誌。在SQL Server 2000時代,只能通過Trace flag 1204來開啟,由於Trace flag 1204並不能提供XML死鎖圖,在SQL Server 2005以及之後的版本被Trace flag 1222所取代。

    為了在服務端針對所有的Session開啟Trace flag 1222。可以通過如代碼1所示。

DBCC TRACEON(1222,-1)

    代碼1.針對所有Session開啟1222這個Trace Flag

    除去代碼1之外,還可以通過在啟動SQL Server實例之前,對加啟動參數 –t1222。這裡就不再細說了。

    此時,當發生死鎖後,就能從日誌看到相關的記錄,如圖5所示。

    5     圖5.死鎖後的記錄

 

通過Profiler來查看死鎖   

    另一種方法是開啟Profiler來捕捉,Profiler捕捉到的圖示死鎖信息內容就更直觀了,Profiler的設置如圖6所示。

    6

    圖6.Profiler中抓死鎖圖的設置

 

    所抓到的死鎖圖如圖7所示。

    7

    圖7.死鎖圖

 

    通過這個死鎖圖,可以更直觀的看到死鎖產生的主體和資源,並且滑鼠移到主體上時,還可以顯示造成死鎖的語句。死鎖的犧牲品進程會被打X號。

    上面的死鎖圖還可以看到造成死鎖的資源。

 

SQL Server中產生死鎖的一些情況

 

由書簽查找產生的死鎖

     這類死鎖產生的原因是書簽查找和更新數據產生的僵持狀態。簡單來說,就是由於Update語句對基本表產生X鎖,然後需要對錶上的索引也進行更新,而表上的索引正好被另一個連接進行查找,加了S鎖,此時又產生書簽查找去基本表加了X鎖的數據進行書簽查找,此時形成死鎖,這個概念可以從圖8看到。

    8

    圖8.由書簽查找產生的死鎖

 

    這種死鎖可以通過Include列來減少書簽查找,從而減少這種類型死鎖發生的概率。

 

由外鍵產生的死鎖

    這類死鎖產生的原因來自外鍵約束。當主表(也就是主鍵是從表外鍵的那個表)更新數據時,需要查看從表,以確定從表的外鍵列滿足外鍵約束。此時會在主表上加X鎖,但這並不能阻止同一時間,另一個SPID向從表添加被修改的主表主鍵,為瞭解決這個問題,SQL Server在進行這類更新時,使用Range鎖,這種鎖是當隔離等級為序列化時才有的,因此在這時雖然隔離等級可能是預設的已提交讀,但是行為卻是序列化。這很可能就會導致死鎖。

    解決辦法之一是向外鍵列添加索引,使得Range鎖加在索引上,而不是表本身。從而降低了死鎖發生的概率。

   

由於推進順序不當產生的死鎖

    這也是圖3中死鎖的原因。在多個事務對資源的使用順序不當,形成死鎖環路而引發的。解決方法是儘量是資源的使用順序一致。這也是死鎖問題出現最多的一種情況。

 

如何減少死鎖

    上面簡單講述了SQL Server中產生死鎖的一些情況。下麵我們從更寬泛的角度來看如何減少死鎖。

    在操作系統中,進程併發減少死鎖的原理同樣可以套用到SQL Server中。在操作系統對於處理死鎖的辦法如下:

1) 預防死鎖。

這是一種較簡單和直觀的事先預防的方法。方法是通過設置某些限制條件,去破壞產生死鎖的四個必要條件中的一個或者幾個,來預防發生死鎖。預防死鎖是一種較易實現的方法,已被廣泛使用。但是由於所施加的限制條件往往太嚴格,可能會導致系統資源利用率和系統吞吐量降低。

2) 避免死鎖。

該方法同樣是屬於事先預防的策略,但它並不須事先採取各種限制措施去破壞產生死鎖的的四個必要條件,而是在資源的動態分配過程中,用某種方法去防止系統進入不安全狀態,從而避免發生死鎖。

3)檢測死鎖。

這種方法並不須事先採取任何限制性措施,也不必檢查系統是否已經進入不安全區,此方法允許系統在運行過程中發生死鎖。但可通過系統所設置的檢測機構,及時地檢測出死鎖的發生,並精確地確定與死鎖有關的進程和資源,然後採取適當措施,從系統中將已發生的死鎖清除掉。

4)解除死鎖。

這是與檢測死鎖相配套的一種措施。當檢測到系統中已發生死鎖時,須將進程從死鎖狀態中解脫出來。常用的實施方法是撤銷或掛起一些進程,以便回收一些資源,再將這些資源分配給已處於阻塞狀態的進程,使之轉為就緒狀態,以繼續運行。死鎖的檢測和解除措施,有可能使系統獲得較好的資源利用率和吞吐量,但在實現上難度也最大。

 

    由上面4中處理死鎖的辦法看,其中檢測死鎖和解除死鎖是Lock Monitor的事,作為DBA或資料庫開發人員,處理死鎖要放在預防和避免死鎖上。

 

預防死鎖

    預防死鎖就是破壞四個必要條件中的某一個和幾個,使其不能形成死鎖。有如下幾種辦法

1)破壞互斥條件

    破壞互斥條件有比較嚴格的限制,在SQL Server中,如果業務邏輯上允許臟讀,則可以通過將隔離等級改為未提交讀或使用索引提示。這樣使得讀取不用加S鎖,從而避免了和其它查詢所加的與S鎖不相容的鎖互斥,進而減少了死鎖出現的概率。

2)破壞請求和等待條件

    這點由於事務存在原子性,是不可破壞的,因為解決辦法是儘量的減少事務的長度,事務內執行的越快越好。這也可以減少死鎖出現的概率。

3)破壞不剝奪條件

    由於事務的原子性和一致性,不剝奪條件同樣不可破壞。但我們可以通過增加資源和減少資源占用兩個角度來考慮。

    增加資源:比如說通過建立非聚集索引,使得有了額外的資源,查詢很多時候就不再索要鎖基本表,轉而鎖非聚集索引,如果索引能夠“覆蓋(Cover)”查詢,那更好不過。因此索引Include列不僅僅減少書簽查找來提高性能,還能減少死鎖。增加資源還可以通過SQL Server 2005之後的行版本控制進行,但這種方式並不推薦,在此不再詳細討論。

    減少資源占用:比如說查詢時,能用select col1,col2這種方式,就不要用select * .這有可能帶來不必要的書簽查找

 

避免死鎖

    避免死鎖是在有限的資源下,使得主體爭用資源不形成環路。比如說典型的銀行家演算法,就是在資源有限的情況下,在不造成現金流斷裂的情況下,儘可能多的按一定順序分配資源。

    因此避免死鎖的關鍵是“順序”。在SQL Server中,儘量使查詢對資源的使用順序保持一致。比如圖3就是一個典型的不按順序請求資源而導致的死鎖。假設圖3的順序改為圖9所示順序,那是形不成死鎖的,轉而,死鎖會變為等待。

    9

    圖9.按順序,死鎖轉為等待

 

SQL Server中死鎖的處理

    那既然死鎖無法避免,在出現死鎖的時候要有一種處理機制。可以想象一下,如果你的程式是一個電子商務網站,由於死鎖造成用戶的生成的訂單被RollBack…

    因此死鎖的處理在SQL Server可以放在兩個層面進行

 

在SQL Server層面處理死鎖

    首先要知道,SQL Server中死鎖的錯誤代碼是1205,由於死鎖是由阻塞引起的,而阻塞的時間往往都不長,索引可以通過重試幾次來處理死鎖,典型的代碼如代碼2所示。

--重試次數
DECLARE @retry INT

SET @retry = 3

WHILE ( @retry > 0 ) 
    BEGIN
        BEGIN TRY
   --這裡是業務代碼
   
   --事務成功,將重試次數變為
            SET @retry = 0
        END TRY
   
        BEGIN CATCH
   --如果是死鎖,則重試
            IF ( ERROR_NUMBER() = 1205 ) 
                SET @retry = @retry
            ELSE 
                BEGIN
      --如果是其它錯誤,記錄到日誌等..
                END
      
        END CATCH
    END

    代碼2.在SQl Server層面處理死鎖

 

在程式層處理死鎖

   和SQL Server中處理死鎖的方式大同小異,也是通過錯誤代碼進行判斷,下麵是C#處理死鎖的方式如代碼3所示。

int retry = 3;
        while (retry > 0)
        {
            try
            {
                //執行sql語句的代碼
                //將重試次數變為0
                retry = 0;
            }
            catch(SqlException e)
            {
                //如果是死鎖的話,0.5S後重試
                if(e.Number==1205)
                {
                    System.Threading.Thread.Sleep(500);
                    retry--;
                }
                //其它錯誤....
                else
                {
                    throw;
                }
            }
        }

   代碼3.死鎖處理的C#代碼

 

總結

    本文講述了死鎖的概念,產生死鎖的四個必要條件,死鎖的處理方式和在SQL Server中如何檢測避免和處理死鎖。死鎖是由於阻塞引起的,瞭解這部分基本概念對於死鎖方面的排錯是非常必要的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 新建項目 iOS | Framework & Library Cocoa Touch Static Library 新建一個Library庫 1. M.h頭文件 2. M.c實現文件 cmd + B 編譯,此時只編譯模擬器了版本,可以連接手機編譯真機版本靜態庫文件,編譯成功後會在電腦上生成相關的.a ...
  • 1. 什麼是UIApplication UIApplication 對象是應用程式的象徵,不能手動創建,不能 alloc init,一個應用程式只允許 一個 。 每個應用都有自己的 UIApplication 對象,而且是單例。 UIApplication 對象是應用程式的象徵,不能手動創建,不能 ...
  • 1.表格(UITableView)與表格控制器(UITableViewController) UITableView是iOS開發中常見的UI控制項,本質是一個列表(單列的表格)。UITableView允許自由控制行的控制項,包括在表格行中添加多個字控制項。UITableView繼承了UIScrollVie ...
  • ...
  • 軟體測試的分類* 黑盒測試 * 測試邏輯業務* 白盒測試 * 測試邏輯方法 根據測試粒度 * 方法測試:function test * 單元測試:unit test * 集成測試:integration test * 系統測試:system test 根據測試暴力程度 * 冒煙測試:smoke te ...
  • SecondaryNameNode是用來合併fsimage和edits文件來更新NameNode和metadata的。 其工作流程為: 1.secondary通知namenode切換edits文件 2.secondary從namenode獲得fsimage和edits(通過http) 3.secon ...
  • DAYOFWEEK(date) 返回日期date是星期幾(1=星期天,2=星期一,……7=星期六,ODBC標準)mysql> select DAYOFWEEK('1998-02-03'); -> 3 WEEKDAY(date) 返回日期date是星期幾(0=星期一,1=星期二,……6= 星期天)。  ...
  • (1) 選擇最有效率的表名順序(只在基於規則的優化器中有效): Oracle的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表。假如有3個以上的表連 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...