共用方式為


死鎖指南

適用於:SQL ServerAzure SQL DatabaseAzure SQL Managed InstanceAzure Synapse Analytics分析平台系統(PDW)Microsoft Fabric 中的 SQL 資料庫

本文會深入探討資料庫引擎中的死結。 鎖死會導致資料庫中競爭且同時鎖定,通常是在多步驟異動中。 如需交易和鎖定的詳細資訊,請參閱 交易鎖定和資料列版本控制指南

如需識別和防止 Azure SQL Database 和 Fabric 中的 SQL Database 中死結的更具體資訊,請參閱 分析和防止 Azure SQL Database 和 Fabric 中的 SQL Database 中的死結

瞭解死結

當兩個或多個工作各自具鎖定某個資源,但其他工作嘗試要鎖定此資源,而造成工作永久封鎖彼此時,會發生鎖死。 例如:

  • 交易 A 需要資料列 1 的共用鎖定。

  • 交易 B 需要資料列 2 的共用鎖定。

  • 交易 A 現在要求資料列 2 的獨佔鎖定,但會被封鎖直到交易 B 完成並釋出對資料列 2 的共用鎖定為止。

  • 交易 B 現在要求資料列 1 的獨佔鎖定,但會被封鎖直到交易 A 完成並釋出對資料列 1 的共用鎖定為止。

等到異動 B 完成後,異動 A 才能完成,但異動 B 卻被異動 A 封鎖了。這個狀況也稱為「循環相依性」:異動 A 相依於異動 B,且異動 B 因為相依於異動 A 而形成封閉式循環。

在鎖死中的這兩個異動會一直等下去,除非由外部處理序解除此鎖死。 資料庫引擎死結監視器會定期檢查處於死結中的工作。 如果監視器偵測到循環相依性,它會選擇其中一個工作作為犧牲者,以錯誤來結束其交易。 這樣另一個工作便可以完成其交易。 因為錯誤而結束異動的應用程式可以重試異動,通常在另一個鎖死異動完成之後便會完成。

死結通常會和一般的封鎖產生混淆。 當交易要求鎖定的資源被另一個交易鎖定時,提出要求的交易會等待鎖定釋出。 根據預設,資料庫引擎中的交易處理不會超時,除非 LOCK_TIMEOUT 已設定。 提出要求的異動會被封鎖,但非鎖死,因為提出要求的異動尚未封鎖目前擁有鎖定的異動。 最後,主控異動會完成並釋出鎖定,然後提出要求的異動會被授與鎖定並繼續進行。 鎖死會近乎立即解決,而封鎖理論上可以無限期持續。 死結 (Deadlock) 有時也稱為致命環節 (Deadly Embrace)。

鎖死可能發生在任何具有多執行緒的系統上,而不只是在關聯式資料庫管理系統,並且可能發生在資料庫物件鎖定之外的資源。 例如,在多執行緒作業系統中的一個執行緒可能取得一或多個資源,像是記憶體區塊。 若要取得的資源目前為另一個執行緒所擁有,前者的執行緒可能必須等候擁有資源的執行緒釋放目標資源。 等候的執行緒便是所謂的與擁有該特定資源的執行緒具有依存性。 在資料庫引擎的執行個體中,連線可能會在取得非資料庫資源 (例如記憶體或執行緒)時發生死結。

顯示交易鎖死的圖表。

在上圖中,在 Part 資料表鎖定資源上,交易 T1 相依於交易 T2。 同樣的,在 Supplier 資料表鎖定資源上,交易 T2 相依於交易 T1。 由於這些相依性形成循環,異動 T1 與 T2 之間便構成鎖死。

這裡有個更一般的死鎖例子:

顯示處於鎖死狀態工作的圖表。

  • 工作 T1 有資源 R1 的鎖定 (由 R1 到 T1 的箭頭所表示),並且已要求資源 R2 的鎖定 (由 T1 到 R2 的箭頭所表示)。

  • 工作 T2 有資源 R2 的鎖定 (由 R2 到 T2 的箭頭所表示),並且已要求資源 R1 的鎖定 (由 T2 到 R1 的箭頭所表示)。

  • 因為在有資源可用之前,沒有一項工作可以繼續,而在有工作繼續之前,沒有一項資源可以釋放,所以會有死結狀態。

Note

資料庫引擎會自動偵測死結週期。 程序會選擇其中一個事務作為死鎖受害者,並以錯誤終止該事務以中斷死鎖。

可能鎖死的資源

每個使用者工作階段可能有一或多個工作代表工作階段執行,其中每個工作可能會取得或等待取得資源。 下列類型的資源會導致封鎖,而造成死結。

  • Locks. 等待取得像是物件、分頁、資料列、中繼資料和應用程式等資源的鎖定,可能會導致鎖死。 例如,交易 T1 在列 r1 上具有共用 (S) 鎖,且正在等待取得 r2 上的獨佔 (X) 鎖。 交易 T2 在 r2 上具有共用 (S) 鎖定,且正在等待取得列 r1 上的獨佔 (X) 鎖定。 這樣會產生鎖定循環,因為 T1 和 T2 都在等待對方釋放已鎖定的資源。

  • 工作執行緒。 等待可用背景工作執行緒的佇列工作,可能會導致鎖死。 如果佇列工作擁有正在封鎖所有工作者執行緒的資源,便會產生鎖死。 例如,會話 S1 啟動交易並取得資料列 r1 上的共用 (S) 鎖定,然後進入睡眠狀態。 在所有可用的工作執行緒上執行的作用中的會話正在嘗試取得列 r1 上的獨佔X鎖定。 因為工作階段 S1 無法取得工作者執行緒,所以它無法認可異動並釋放資料列 r1 的鎖定。 這樣會產生死結。

  • Memory. 當並行要求正在等待記憶體授權,但可用記憶體不足而無法滿足授權時,便會發生鎖死。 例如,兩個並行查詢 Q1 和 Q2 以使用者定義函式執行,分別取得 10 MB 和 20 MB 記憶體。 如果每個查詢需要 30 MB,而可用的總記憶體是 20 MB,則 Q1 和 Q2 必須等待對方釋出記憶體,這樣會導致鎖死。

  • 與平行查詢執行相關的資源。 與交換通訊埠建立關聯的協調器、產生器或取用者執行緒,若包含至少一個不屬於平行查詢的其他處理序,通常可能會彼此封鎖,而導致鎖死。 此外,當平行查詢開始執行時,資料庫引擎會根據目前的工作負載,判斷平行處理原則的程度,以及所需的背景工作執行緒數目。 如果系統工作負載意外變更,例如有新查詢開始在伺服器上執行或系統的工作者執行緒用盡,此時會發生死結。

  • Multiple Active Result Set (MARS) 資源。 這些資源在 MARS 下是用來控制多個使用中要求的交錯情形。 如需詳細資訊,請參閱在 SQL Server Native Client 使用 Multiple Active Result Sets (MARS)

    • 使用者資源。 當執行緒正在等待的資源可能受使用者應用程式控制時,此資源會視為外部或使用者資源,並且如同鎖定處理。

    • 會話互斥。 工作階段中執行的工作為交錯的,這表示同時只能有一個工作在此工作階段執行。 工作必須具有對工作階段 Mutex 的獨佔存取權才能執行。

    • 交易互斥鎖。 交易中執行的所有工作為交錯的,這表示同時只能有一個工作在此交易執行。 工作必須具有對交易 Mutex 的獨佔存取權才能執行。

      工作必須取得工作階段 Mutex,才能在 MARS 下執行。 如果工作在交易下執行,則它必須接著取得交易 Mutex。 這可保證在給定工作階段和給定交易中,同時只有一個工作使用中。 一旦取得所需的 Mutex,工作即可執行。 當工作完成或在要求中途退出時,會以取得 Mutex 的相反順序,先釋放異動 Mutex,接著再釋放工作階段 Mutex。 不過,這些資源可能會發生死結。 在下列虛擬程式碼中,兩個工作 (使用者要求 U1 和使用者要求 U2) 在同一個工作階段中執行。

      U1:    Rs1=Command1.Execute("insert sometable EXEC usp_someproc");
      U2:    Rs2=Command2.Execute("select colA from sometable");
      

      從使用者要求 U1 執行的預存程序已取得工作階段 Mutex。 如果預存程序需要很長時間才能執行,資料庫引擎會假設預存程序正在等候使用者的輸入。 當使用者正在等待 U2 的結果集時,使用者要求 U2 正在等待工作階段 Mutex,而 U1 正在等待使用者資源。 這就是死結狀態,邏輯上可用下圖說明:

      MARS 中預存程序邏輯流程的圖表。

當資料表已分割,且 LOCK_ESCALATIONALTER TABLE 設定為 AUTO 時,也可能發生鎖死。 當 LOCK_ESCALATION 設定為 AUTO時,允許資料庫引擎在 HoBT 層級鎖定資料表分割區,而不是在資料表層級鎖定資料表分割區,以增加並行性。 但是,當個別交易在資料表中保留資料分割鎖定,而且想要鎖定其他交易資料分割上的某個地方時,這就會造成死結。 您可以將 設定 LOCK_ESCALATIONTABLE來避免這種類型的鎖死。 不過,此設定會藉由強制對分割區進行大型更新來等候資料表鎖定,以減少並行。

死鎖偵測

[可以死結的資源] 一節中列出的所有資源都會參與資料庫引擎死結偵測配置。 死結偵測由鎖定監視器執行緒進行,該執行緒定期搜尋資料庫引擎實例中的所有任務。 下列幾點描述搜尋程序:

  • 預設間隔是 5 秒。

  • 如果鎖定監視器執行緒發現死結,死結偵測間隔會從 5 秒降低,最低降至 100 毫秒,視死結頻率而定。

  • 如果鎖定監視器執行緒停止尋找死結,資料庫引擎會將搜尋之間的間隔增加到 5 秒。

  • 如果偵測到死結,則假設必須等候鎖定的新執行緒正在進入死結週期。 在偵測到鎖死之後,前面幾個鎖定等待會立即觸發鎖死搜尋,而不需等到下個鎖死偵測間隔。 例如,如果目前間隔是 5 秒,並且剛偵測到鎖死,則下個鎖定等待會立即啟動鎖死偵測設定。 如果此鎖定等候是死結的一部分,則會立即偵測到它,而不是在下一個死結搜尋期間偵測到它。

資料庫引擎通常只會執行定期死結偵測。 因為系統中遇到的死鎖數目通常很少,所以定期死鎖偵測有助於減少系統中死鎖偵測的額外負擔。

當鎖定監視執行緒為特定的執行緒啟動死結搜尋時,便對執行緒正在等候的資源進行識別。 而後再由鎖定監視找出該特定資源的擁有者執行緒,並繼續為這些執行緒進行遞迴的死結搜尋直到找出循環為止。 以此方式識別到的循環即構成死結。

偵測到死結之後,資料庫引擎會選擇其中一個執行緒作為死結受害者,以結束死結。 資料庫引擎會終止針對執行緒執行的目前批次、復原死結受害者的交易,並將錯誤 1205 傳回給應用程式。 回復死結犧牲者的交易,將會釋放交易所持有的所有鎖定。 這可讓其他執行緒的交易變成解除封鎖的狀態並繼續進行。 1205 (死鎖受害者) 錯誤會記錄死結中涉及的資源類型的相關資訊。

根據預設,資料庫引擎會選擇復原成本最低的交易作為死結受害者。 或者,使用者可使用 SET DEADLOCK_PRIORITY 陳述式來指定在發生死結時工作階段的優先順序。 DEADLOCK_PRIORITY 可以設定為 LOWNORMALHIGH,或者可以設定為 -10 到 10 範圍內的任何整數值。 在某些情況下,資料庫引擎可能會選擇在短時間內變更死結優先順序,以達到更好的並行性。

死鎖優先順序預設為 NORMAL或 0。 如果兩個階段作業具有不同的死鎖優先順序,則會選擇優先順序較低的階段作業上的交易作為死鎖受害者。 如果兩個階段作業具有相同的死鎖優先順序,則會選擇復原成本最低的交易。 如果死結循環中相關的工作階段具有相同的死結優先權和相同成本,則會隨機選擇犧牲者。 回復中的工作不可選為鎖死犧牲者。

使用共用語言執行階段 (CLR) 時,死結監視器會自動偵測在受控程序內存取的同步處理資源 (監視器、讀取器/寫入器鎖定和執行緒聯結) 的死結。 不過,死結是透過在選為死結犧牲者的程序中擲回例外狀況來解決。 例外狀況並不會自動釋放犧牲者目前所擁有的資源;您必須明確釋放資源,了解這點很重要。 與例外狀況行為一致,用來識別死結犧牲者的例外狀況可以在發生後解除。

鎖死資訊工具

若要檢視死結資訊,資料庫引擎會以擴充事件、兩個追蹤旗標,以及 SQL Profiler 中的死結圖形事件的形式 xml_deadlock_report 提供監視工具。

xml_deadlock_report擴展事件是建議用於擷取死結資訊的方法。

死結擴展事件

在 SQL Server 2012 (11.x) 和更新版本中, xml_deadlock_report 應該使用擴充事件,而不是 SQL 追蹤或 SQL 分析器中的死結圖形事件類別。

預設情況下, system_health 事件會階段會擷取 xml_deadlock_report 事件。 這些事件包含死鎖圖形。 由於此會話是預設啟用的,因此您不需要設定個別的事件會話來捕捉死鎖資訊。

所擷取的 Deadlock Graph 通常有三個不同的節點:

  • victim-list。 死結犧牲者處理序識別碼。
  • process-list。 涉及死結之所有處理序的相關資訊。
  • resource-list。 涉及死結之資源的相關資訊。

您可以在 Management Studio 中檢視 event_file 工作階段的目標 system_health 資料。 如果發生任何 xml_deadlock_report 事件,Management Studio 會以圖形方式描述死結中涉及的工作和資源,如下列範例所示:

XEvent 鎖死圖形視覺圖表的 SSMS 螢幕擷取畫面。

下列查詢可以檢視所有ring_buffer工作階段的system_health目標所捕捉的死結事件:

SELECT xdr.value('@timestamp', 'datetime') AS deadlock_time,
       xdr.query('.') AS event_data
FROM (SELECT CAST ([target_data] AS XML) AS target_data
      FROM sys.dm_xe_session_targets AS xt
           INNER JOIN sys.dm_xe_sessions AS xs
               ON xs.address = xt.event_session_address
      WHERE xs.name = N'system_health'
            AND xt.target_name = N'ring_buffer') AS XML_Data
CROSS APPLY Target_Data.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS XEventData(xdr)
ORDER BY deadlock_time DESC;

結果集如下所示。

system_health XEvent 查詢結果的 SSMS 螢幕擷取畫面。

下列範例顯示event_data欄位的輸出:

<event name="xml_deadlock_report" package="sqlserver" timestamp="2022-02-18T08:26:24.698Z">
  <data name="xml_report">
    <type name="xml" package="package0" />
    <value>
      <deadlock>
        <victim-list>
          <victimProcess id="process27b9b0b9848" />
        </victim-list>
        <process-list>
          <process id="process27b9b0b9848" taskpriority="0" logused="0" waitresource="KEY: 5:72057594214350848 (1a39e6095155)" waittime="1631" ownerId="11088595" transactionname="SELECT" lasttranstarted="2022-02-18T00:26:23.073" XDES="0x27b9f79fac0" lockMode="S" schedulerid="9" kpid="15336" status="suspended" spid="62" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2022-02-18T00:26:22.893" lastbatchcompleted="2022-02-18T00:26:22.890" lastattention="1900-01-01T00:00:00.890" clientapp="SQLCMD" hostname="ContosoServer" hostpid="7908" loginname="CONTOSO\user" isolationlevel="read committed (2)" xactid="11088595" currentdb="5" lockTimeout="4294967295" clientoption1="538968096" clientoption2="128056">
            <executionStack>
              <frame procname="AdventureWorks2022.dbo.p1" line="3" stmtstart="78" stmtend="180" sqlhandle="0x0300050020766505ca3e07008ba8000001000000000000000000000000000000000000000000000000000000">
SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+    </frame>
              <frame procname="adhoc" line="4" stmtstart="82" stmtend="98" sqlhandle="0x020000006263ec01ebb919c335024a072a2699958d3fcce60000000000000000000000000000000000000000">
unknown    </frame>
            </executionStack>
            <inputbuf>
SET NOCOUNT ON
WHILE (1=1)
BEGIN
    EXEC p1 4
END
   </inputbuf>
          </process>
          <process id="process27b9ee33c28" taskpriority="0" logused="252" waitresource="KEY: 5:72057594214416384 (e5b3d7e750dd)" waittime="1631" ownerId="11088593" transactionname="UPDATE" lasttranstarted="2022-02-18T00:26:23.073" XDES="0x27ba15a4490" lockMode="X" schedulerid="6" kpid="5584" status="suspended" spid="58" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2022-02-18T00:26:22.890" lastbatchcompleted="2022-02-18T00:26:22.890" lastattention="1900-01-01T00:00:00.890" clientapp="SQLCMD" hostname="ContosoServer" hostpid="15316" loginname="CONTOSO\user" isolationlevel="read committed (2)" xactid="11088593" currentdb="5" lockTimeout="4294967295" clientoption1="538968096" clientoption2="128056">
            <executionStack>
              <frame procname="AdventureWorks2022.dbo.p2" line="3" stmtstart="76" stmtend="150" sqlhandle="0x03000500599a5906ce3e07008ba8000001000000000000000000000000000000000000000000000000000000">
UPDATE t1 SET c2 = c2+1 WHERE c1 = @p    </frame>
              <frame procname="adhoc" line="4" stmtstart="82" stmtend="98" sqlhandle="0x02000000008fe521e5fb1099410048c5743ff7da04b2047b0000000000000000000000000000000000000000">
unknown    </frame>
            </executionStack>
            <inputbuf>
SET NOCOUNT ON
WHILE (1=1)
BEGIN
    EXEC p2 4
END
   </inputbuf>
          </process>
        </process-list>
        <resource-list>
          <keylock hobtid="72057594214350848" dbid="5" objectname="AdventureWorks2022.dbo.t1" indexname="cidx" id="lock27b9dd26a00" mode="X" associatedObjectId="72057594214350848">
            <owner-list>
              <owner id="process27b9ee33c28" mode="X" />
            </owner-list>
            <waiter-list>
              <waiter id="process27b9b0b9848" mode="S" requestType="wait" />
            </waiter-list>
          </keylock>
          <keylock hobtid="72057594214416384" dbid="5" objectname="AdventureWorks2022.dbo.t1" indexname="idx1" id="lock27afa392600" mode="S" associatedObjectId="72057594214416384">
            <owner-list>
              <owner id="process27b9b0b9848" mode="S" />
            </owner-list>
            <waiter-list>
              <waiter id="process27b9ee33c28" mode="X" requestType="wait" />
            </waiter-list>
          </keylock>
        </resource-list>
      </deadlock>
    </value>
  </data>
</event>

追蹤標誌 1204 和 追蹤標誌 1222

發生死結且啟用追蹤旗標 1204 或追蹤旗標 1222 時,會在 SQL Server 錯誤記錄檔中報告死結詳細資料。 追蹤旗標 1204 會報告死結中涉及的每一個節點所格式化的死結資訊。 追蹤旗標 1222 會先依處理程序格式化死結資訊,然後依資源進行格式化。 可同時啟用兩種追蹤旗標來取得相同鎖死事件的兩種表示法。

Important

避免在遇到死結的工作量密集型系統上使用追蹤旗標 1204 和 1222。 使用此等追蹤旗標可能會導致效能問題。 相反地,請使用 Deadlock 擴充事件 來擷取必要的資訊。

除了定義追蹤旗標 1204 和 1222 的屬性之外,下表也顯示相似性和差異性。

Property 追蹤標誌 1204 和 追蹤標誌 1222 僅限追蹤標 1204 僅限使用 追蹤旗標 1222
輸出格式 輸出是在 SQL Server 錯誤記錄檔中擷取。 聚焦於死結所涉及的節點。 每一個節點有一個專用區段,最後區段描述死結犧牲者。 以類似 XML 格式傳回不符合 XML 結構描述定義 (XSD) 結構描述的資訊。 此格式有三大區段。 第一個區段宣告死結犧牲者。 第二個區段描述死結所涉及的每一個處理序。 第三節說明與追蹤旗標 1204 中的節點同義的資源。
識別屬性 SPID:<x> ECID:<x>. 在平行處理的情況下識別會話標識碼線程。 SPID:<x> ECID:0 項目代表主執行緒,其中 <x> 會以 SPID 值取代。 條目 SPID:<x> ECID:<y>,其中 <x> 會被替換為 SPID 值,且 <y> 大於 0,代表相同 SPID 的執行內容。

BatchIDsbid 用於追蹤標誌 1222)。 識別程式碼執行從中要求或保留鎖定的批次。 停用 Multiple Active Result Sets (MARS) 時,BatchID 值為 0。 啟用 MARS 時,作用中批次的值為 1 到 n。 如果工作階段中沒有作用中批次,BatchID 為 0。

Mode 指定執行緒所要求、授與或等待之特定資源的鎖定類型。 模式可以是意圖共用IS、共用S、更新U、意圖排他IX、與意圖共用SIX、和排他X

Line #line 用於追蹤標誌 1222)。 列出發生死結時正在執行之目前陳述式批次中的行號。

Input Bufinputbuf 用於追蹤標誌 1222)。 列出目前批次中的所有陳述式。
Node 代表死結鏈結中的項目號碼。

Lists 鎖定擁有者可以是這些清單的一部分:

Grant List 列舉資源的目前擁有者。

Convert List 列舉嘗試將其鎖定轉換為更高層的目前擁有者。

Wait List 列舉資源的目前最新鎖定要求。

Statement Type描述執行緒具有權限的陳述式類型 (SELECTINSERTUPDATE、 或 DELETE)。

Victim Resource Owner 指定資料庫引擎選擇作為受害者的參與執行緒,以中斷死結週期。 所選執行緒及其所有執行環境定義都會終止。

Next Branch 代表死結週期中涉及相同 SPID 的兩個或多個執行內容。
deadlock victim 代表被選為鎖死犧牲者之工作 (請參閱 sys.dm_os_tasks) 的實體記憶體位址。 在未解決的死結的情況下,值可能為零。

executionstack 代表發生死結時正在執行的 Transact-SQL 呼叫堆疊。

priority 代表死結優先權。

logused 工作所使用的記錄檔空間。

owner id 具有要求控制權之交易的識別碼。

status 工作的狀態。 如需詳細資訊,請參閱 sys.dm_os_tasks

waitresource 工作所需的資源。

waittime 等待資源的時間 (以毫秒為單位)。

schedulerid 與此工作相關聯的排程器。 參見 sys.dm_os_schedulers

hostname 工作站的名稱。

isolationlevel 目前交易隔離等級。

Xactid 具有要求控制權之交易的識別碼。

currentdb 資料庫的識別碼。

lastbatchstarted 用戶端處理序上次啟動批次執行的時間。

lastbatchcompleted 用戶端處理序上次完成批次執行的時間。

clientoption1clientoption2 此會話的設定選項。 這些值是位遮罩,代表通常由SET語句控制的選項,例如SET NOCOUNTSET XACTABORT。 如需詳細資訊,請參閱 @@OPTIONS

associatedObjectId 代表 HoBT (堆積或 B 型樹狀結構) 識別碼。
資源屬性 RID 識別在資料表內保留或要求鎖定的單一資料列。 RID 是以 RID: db_id:file_id:page_no:row_no表示。 例如: RID: 6:1:20789:0

OBJECT 識別保留或要求鎖定的資料表。 在 OBJECT 中以 OBJECT: db_id:object_id 表示。 例如: TAB: 6:2009058193

KEY 識別在索引內保留或要求鎖定的索引鍵範圍。 KEY 是以 KEY: db_id:hobt_id (index key hash value) 表示。 例如: KEY: 6:72057594057457664 (350007a4d329)

PAG 識別保留或要求鎖定的頁面資源。 在 PAG 中以 PAG: db_id:file_id:page_no 表示。 例如: PAG: 6:1:20789

EXT 識別範圍結構。 在 EXT 中以 EXT: db_id:file_id:extent_no 表示。 例如: EXT: 6:1:9

DB 識別資料庫鎖定。 DB 會以下列其中一種方式表示:

DB: db_id

DB: db_id[BULK-OP-DB],這會識別資料庫備份所採取的資料庫鎖定。

DB: db_id[BULK-OP-LOG],這會識別記錄備份所採取的鎖定。

APP 識別應用程式鎖。 在 APP 中以 APP: lock_resource 表示。 例如: APP: Formf370f478

METADATA 代表死結所涉及的中繼資料資源。 因為 METADATA 有許多子資源,所以傳回的值視含有鎖死的子資源而定。 例如 METADATA.USER_TYPE 會傳回 user_type_id = *integer_value*。 如需有關 METADATA 資源和子資源的詳細資訊,請參閱 sys.dm_tran_locks (Transact-SQL)

HOBT 代表鎖死所涉及的堆積或 B 型樹狀結構。
None 與此追蹤旗標互斥。 None 與此追蹤旗標互斥。

追蹤旗標 1204 範例

下列範例顯示開啟追蹤旗標 1204 時的輸出。 在此案例中,節點 1 中的資料表是一個不含索引的堆積,節點 2 中的資料表是一個含非叢集索引的堆積。 當發生死結時,正在更新節點 2 中的索引鍵。

Deadlock encountered .... Printing deadlock information
Wait-for graph

Node:1

RID: 6:1:20789:0               CleanCnt:3 Mode:X Flags: 0x2
 Grant List 0:
   Owner:0x0315D6A0 Mode: X
     Flg:0x0 Ref:0 Life:02000000 SPID:55 ECID:0 XactLockInfo: 0x04D9E27C
   SPID: 55 ECID: 0 Statement Type: UPDATE Line #: 6
   Input Buf: Language Event:
BEGIN TRANSACTION
   EXEC usp_p2
 Requested By:
   ResType:LockOwner Stype:'OR'Xdes:0x03A3DAD0
     Mode: U SPID:54 BatchID:0 ECID:0 TaskProxy:(0x04976374) Value:0x315d200 Cost:(0/868)

Node:2

KEY: 6:72057594057457664 (350007a4d329) CleanCnt:2 Mode:X Flags: 0x0
 Grant List 0:
   Owner:0x0315D140 Mode: X
     Flg:0x0 Ref:0 Life:02000000 SPID:54 ECID:0 XactLockInfo: 0x03A3DAF4
   SPID: 54 ECID: 0 Statement Type: UPDATE Line #: 6
   Input Buf: Language Event:
     BEGIN TRANSACTION
       EXEC usp_p1
 Requested By:
   ResType:LockOwner Stype:'OR'Xdes:0x04D9E258
     Mode: U SPID:55 BatchID:0 ECID:0 TaskProxy:(0x0475E374) Value:0x315d4a0 Cost:(0/380)

Victim Resource Owner:
 ResType:LockOwner Stype:'OR'Xdes:0x04D9E258
     Mode: U SPID:55 BatchID:0 ECID:0 TaskProxy:(0x0475E374) Value:0x315d4a0 Cost:(0/380)

追蹤旗標 1222 範例

下列範例顯示開啟追蹤旗標 1222 時的輸出。 在此案例中,有一個資料表是不含索引的堆積,另一個資料表是含非叢集索引的堆積。 在第二份資料表中,當發生死結時,正在更新索引鍵。

deadlock-list
 deadlock victim=process689978
  process-list
   process id=process6891f8 taskpriority=0 logused=868
   waitresource=RID: 6:1:20789:0 waittime=1359 ownerId=310444
   transactionname=user_transaction
   lasttranstarted=2022-02-05T11:22:42.733 XDES=0x3a3dad0
   lockMode=U schedulerid=1 kpid=1952 status=suspended spid=54
   sbid=0 ecid=0 priority=0 transcount=2
   lastbatchstarted=2022-02-05T11:22:42.733
   lastbatchcompleted=2022-02-05T11:22:42.733
   clientapp=Microsoft SQL Server Management Studio - Query
   hostname=TEST_SERVER hostpid=2216 loginname=DOMAIN\user
   isolationlevel=read committed (2) xactid=310444 currentdb=6
   lockTimeout=4294967295 clientoption1=671090784 clientoption2=390200
    executionStack
     frame procname=AdventureWorks2022.dbo.usp_p1 line=6 stmtstart=202
     sqlhandle=0x0300060013e6446b027cbb00c69600000100000000000000
     UPDATE T2 SET COL1 = 3 WHERE COL1 = 1;
     frame procname=adhoc line=3 stmtstart=44
     sqlhandle=0x01000600856aa70f503b8104000000000000000000000000
     EXEC usp_p1
    inputbuf
      BEGIN TRANSACTION
       EXEC usp_p1
   process id=process689978 taskpriority=0 logused=380
   waitresource=KEY: 6:72057594057457664 (350007a4d329)
   waittime=5015 ownerId=310462 transactionname=user_transaction
   lasttranstarted=2022-02-05T11:22:44.077 XDES=0x4d9e258 lockMode=U
   schedulerid=1 kpid=3024 status=suspended spid=55 sbid=0 ecid=0
   priority=0 transcount=2 lastbatchstarted=2022-02-05T11:22:44.077
   lastbatchcompleted=2022-02-05T11:22:44.077
   clientapp=Microsoft SQL Server Management Studio - Query
   hostname=TEST_SERVER hostpid=2216 loginname=DOMAIN\user
   isolationlevel=read committed (2) xactid=310462 currentdb=6
   lockTimeout=4294967295 clientoption1=671090784 clientoption2=390200
    executionStack
     frame procname=AdventureWorks2022.dbo.usp_p2 line=6 stmtstart=200
     sqlhandle=0x030006004c0a396c027cbb00c69600000100000000000000
     UPDATE T1 SET COL1 = 4 WHERE COL1 = 1;
     frame procname=adhoc line=3 stmtstart=44
     sqlhandle=0x01000600d688e709b85f8904000000000000000000000000
     EXEC usp_p2
    inputbuf
      BEGIN TRANSACTION
        EXEC usp_p2
  resource-list
   ridlock fileid=1 pageid=20789 dbid=6 objectname=AdventureWorks2022.dbo.T2
   id=lock3136940 mode=X associatedObjectId=72057594057392128
    owner-list
     owner id=process689978 mode=X
    waiter-list
     waiter id=process6891f8 mode=U requestType=wait
   keylock hobtid=72057594057457664 dbid=6 objectname=AdventureWorks2022.dbo.T1
   indexname=nci_T1_COL1 id=lock3136fc0 mode=X
   associatedObjectId=72057594057457664
    owner-list
     owner id=process6891f8 mode=X
    waiter-list
     waiter id=process689978 mode=U requestType=wait

Profiler 鎖死圖形事件

SQL Profiler 有一個事件,以圖形方式描述死結中涉及的工作和資源。 下列範例顯示開啟 Deadlock Graph 事件時 SQL Profiler 的輸出。

「SQL 側寫器」和「SQL 追蹤」功能已被取代,並由「延伸事件」取代。 擴充事件的效能額外負荷較小,而且比 SQL 追蹤更可設定。 請考慮使用 擴充事件死結事件 ,而不是在 SQL Profiler 中追蹤死結。

SQL 追蹤視覺鎖死圖形 SSMS 的螢幕擷取畫面。

如需有關死結事件的詳細資訊,請參閱 Lock:Deadlock 事件類別。 如需 SQL Profiler 死結圖形的詳細資訊,請參閱 儲存死結圖形 (SQL Server Profiler)。

延伸事件提供 SQL 追蹤事件類別的等價。 如需詳細資訊,請參閱SQL 追蹤事件類別的擴展事件等效項目。 建議使用擴展事件來取代 SQL 追蹤。

處理死結

當資料庫引擎的執行個體選擇交易作為死結受害者時,它會終止目前的批次、復原交易,並將錯誤 1205 傳回給應用程式。 傳回的訊息結構如下:

Your transaction (process ID #...) was deadlocked on {lock | communication buffer | thread} resources with another process and has been chosen as the deadlock victim. Rerun your transaction.

因為任何提交 Transact-SQL 查詢的應用程式都可以選擇為死結受害者,所以應用程式應該有可以處理錯誤 1205 的錯誤處理常式。 如果應用程式未處理錯誤,應用程式可能會繼續進行,而不知道其交易已復原。

實作攔截錯誤 1205 的錯誤處理常式可讓應用程式處理死結並採取補救動作 (例如,自動重新提交死結中涉及的查詢)。

應用程式應該在重新送出查詢之前稍做停頓。 這樣死鎖中涉及的其他交易便有機會完成並釋放其鎖。 隨機化暫停的持續時間,可將重新提交的查詢要求其鎖定時死結再次發生的可能性降到最低。 例如,錯誤處理常式可能會編碼為暫停一到三秒之間的隨機持續時間。

使用 TRY 處理...抓住

您可以使用 TRY...CATCH 來處理鎖死。 區塊 CATCH 可以捕獲錯誤 1205。

如需詳細資訊,請參閱錯誤處理

減少死鎖

即使鎖死無法完全避免,但遵循特定程式碼撰寫的慣例可以將產生鎖死的機會降至最低。 將死結降至最低可以提高交易的產能並降低系統額外負荷,因為較少的交易需要:

  • 回復,將交易所進行的工作全部恢復。
  • 由應用程式重新送出,因為發生死結時已將交易回復。

若要協助將死結降至最低:

  • 以相同的順序來存取物件。
  • 在交易中避免使用者互動。
  • 將交易維持在單一批次中且愈短愈好。
  • 避免使用較高的隔離等級,例如 REPEATABLE READSERIALIZABLE,除非必要。
  • 使用資料列版本設定基礎的隔離等級。
    • 啟用 READ_COMMITTED_SNAPSHOT 資料庫選項,以使用 READ COMMITTED 隔離層次來進行交易的資料列版本控制。
    • 使用快照隔離交易。
  • 使用繫結連接。

以相同的順序存取物件

如果所有同時發生的交易都以相同的順序來存取物件,就比較不會發生死結。 例如,如果同時發生的兩筆交易都取得 Supplier 資料表的鎖定,再取得 Part 資料表的鎖定,其中一筆交易便被封鎖於 Supplier 資料表直到另一筆交易完成為止。 第一筆異動認可或回復之後,第二筆才會繼續,這樣就不會發生鎖死。 使用預存程序來進行所有的資料修改動作可將物件的存取順序標準化。

鎖死的圖表。

在交易中避免使用者互動

避免包含使用者互動的交易,因為在沒有使用者介入的情況下執行批次的速度,遠快於使用者必須手動回應查詢的速度,例如回覆應用程式所要求參數的提示。 如此便會降低系統產能,因為交易所持有的任何鎖定只有在交易被認可或回復之後才會釋放。 即使沒有發生死鎖,在等待交易完成時,存取相同資源的其他交易也會被封鎖。

將交易維持在單一批次中且愈短愈好

死結通常會在許多長時間執行的交易同時執行於相同的資料庫時發生。 交易的時間愈久,持有的獨占或更新鎖定就愈久,因而封鎖了其他的活動並導致發生死結狀況的可能性。

將交易保留在一個批次中可以最大限度地減少交易期間的網路往返,從而減少由於客戶端處理而可能延遲完成交易。

避免更高的隔離等級

判斷是否可以較低的隔離等級執行交易。 使用 READ COMMITTED 可讓交易在不需等待另一個交易完成的情況下,讀取先前由另一個交易讀取(但未修改)的數據。 READ COMMITTED 保持共用鎖定的時間比像 SERIALIZABLE 這樣的較高隔離層級要短。 這可減少鎖定競爭。

使用以資料列版本設定為基礎的隔離等級

設定READ_COMMITTED_SNAPSHOT資料庫選項ON時,在READ COMMITTED隔離層級執行的交易會在讀取作業期間使用行版本控制,而不是使用共用鎖定。

小提示

Microsoft 建議所有應用程式使用資料列版本設定型 READ COMMITTED 隔離層級,除非應用程式依賴鎖定型 READ COMMITTED 隔離層級的封鎖行為。

快照隔離也使用資料列版本設定,不使用讀取作業期間的共用鎖定。 ALLOW_SNAPSHOT_ISOLATION 資料庫選項必須設定為 ON,交易才能在快照集隔離下執行。

使用以資料列版本設定為基礎的隔離層級,將讀取和寫入作業之間可能發生的死結降到最低。

使用繫結連接

使用繫結連接,則同一個應用程式所開啟的二或多個連接可以互相合作。 如果先前由主要連接獲得鎖定,則會讓次要連接持有獲得的鎖定,反之亦然。 因此,他們不會互相阻擋。

導致鎖死

您可能需要造成死鎖以進行學習或示範。

下列範例在具有預設結構描述和資料的範例資料庫中運作,當啟用了 AdventureWorksLT2019 時。 若要下載此範例,請造訪 AdventureWorks 範例資料庫

如需在啟用最佳化鎖定時造成死結的範例,請參閱 最佳化鎖定和死結。

若要造成鎖死,您必須將兩個工作階段連線至 AdventureWorksLT2019 資料庫。 我們將這些會議稱為 會議 A會議 B。您可以在 SQL Server Management Studio (SSMS) 中建立兩個查詢視窗,以建立這兩個工作階段。

工作階段 A 中,執行下列批次。 此程式碼會啟動明確 交易 ,並執行更新 SalesLT.Product 資料表的陳述式。 若要這樣做,交易會在表格中合格的列上取得更新(U)鎖,然後將其轉換為排他()鎖。 我們讓交易保持開啟。

BEGIN TRANSACTION;

UPDATE SalesLT.Product
    SET SellEndDate = SellEndDate + 1
WHERE Color = 'Red';

現在,在 工作階段 B 中,執行下列批次。 此程式碼不會明確開始交易。 相對地,會以自動認可交易模式運作。 此陳述式會更新 SalesLT.ProductDescription 資料表。 更新會對U表格中符合資格的資料列採取更新鎖(SalesLT.ProductDescription)。 查詢會聯結至其他資料表,包括 SalesLT.Product 資料表。

UPDATE SalesLT.ProductDescription
    SET Description = Description
FROM SalesLT.ProductDescription AS pd
     INNER JOIN SalesLT.ProductModelProductDescription AS pmpd
         ON pd.ProductDescriptionID = pmpd.ProductDescriptionID
     INNER JOIN SalesLT.ProductModel AS pm
         ON pmpd.ProductModelID = pm.ProductModelID
     INNER JOIN SalesLT.Product AS p
         ON pm.ProductModelID = p.ProductModelID
WHERE p.Color = 'Silver';

若要完成此更新,工作階段 B 需要對S表格中的資料列,包括SalesLT.Product鎖定的資料列,進行共享 () 鎖定。工作階段 BSalesLT.Product被封鎖。

返回 會議 A。執行下列 UPDATE 陳述式。 此陳述式執行時會作為先前所開啟交易中的一部分。

UPDATE SalesLT.ProductDescription
    SET Description = Description
FROM SalesLT.ProductDescription AS pd
     INNER JOIN SalesLT.ProductModelProductDescription AS pmpd
         ON pd.ProductDescriptionID = pmpd.ProductDescriptionID
     INNER JOIN SalesLT.ProductModel AS pm
         ON pmpd.ProductModelID = pm.ProductModelID
     INNER JOIN SalesLT.Product AS p
         ON pm.ProductModelID = p.ProductModelID
WHERE p.Color = 'Red';

工作階段 A 中的第二個 update 陳述式將會被 上的SalesLT.ProductDescription 封鎖。

工作階段 A工作階段 B 現在會互相封鎖。 這兩個交易各需要另一個交易鎖定的資源,因此都無法繼續。

幾秒鐘之後,鎖死監視器會識別出工作階段 A工作階段 B 中的異動互相封鎖,且兩者都無法進行。 您會看到發生死鎖, 而會話 A 會選擇為死鎖受害者。 工作階段 B 成功完成。 工作階段 A 的查詢視窗中會出現錯誤訊息,其文字類似下列範例:

Msg 1205, Level 13, State 51, Line 7
Transaction (Process ID 51) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

如果未引發鎖死,請確認 READ_COMMITTED_SNAPSHOT 已在範例資料庫中啟用。 鎖死可能發生在任何資料庫設定下,但此範例要求啟用 READ_COMMITTED_SNAPSHOT

您可以在ring_buffer目標system_health事件工作階段中檢視死結的詳細資訊,該會話在預設情況下會在 SQL Server 和 Azure SQL 管理執行個體中啟用且作用中。 請考慮下列查詢:

WITH cteDeadLocks ([Deadlock_XML])
AS (SELECT CAST (target_data AS XML) AS [Deadlock_XML]
    FROM sys.dm_xe_sessions AS xs
         INNER JOIN sys.dm_xe_session_targets AS xst
             ON xs.[address] = xst.event_session_address
    WHERE xs.[name] = 'system_health'
          AND xst.target_name = 'ring_buffer')
SELECT x.Graph.query('(event/data/value/deadlock)[1]') AS Deadlock_XML,
       x.Graph.value('(event/data/value/deadlock/process-list/process/@lastbatchstarted)[1]', 'datetime2(3)') AS when_occurred,
       DB_Name(x.Graph.value('(event/data/value/deadlock/process-list/process/@currentdb)[1]', 'int')) AS DB --Current database of the first listed process
FROM (SELECT Graph.query('.') AS Graph
      FROM cteDeadLocks AS c
      CROSS APPLY c.[Deadlock_XML].nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS Deadlock_Report(Graph)) AS x
ORDER BY when_occurred DESC;

您可以選取顯示為超連結的儲存格,檢視 SSMS 內 Deadlock_XML 資料行中的 XML。 將此輸出儲存為 .xdl 檔案並關閉,然後在 SSMS 中重新開啟 .xdl 檔案,以取得視覺鎖死圖形。 死鎖圖應該如下圖所示。

SSMS 中 .xdl 檔案的視覺鎖死圖形螢幕擷取畫面。

最佳化鎖定和鎖死

使用 最佳化鎖定時,頁面和資料列鎖定要到交易結束才會保留。 一旦更新了一行,它們就會釋放。 此外,如果啟用 READ_COMMITTED_SNAPSHOT,則不會使用更新(U)鎖定。 因此,死鎖的可能性降低。

啟用最佳化鎖定時,上一個範例不會導致死結,因為它依賴於更新 (U) 鎖。

下列範例可用來在已啟用最佳化鎖定的資料庫上造成死結。

首先,建立範例表並新增資料。

CREATE TABLE t2
(
    a INT PRIMARY KEY NOT NULL,
    b INT NULL
);

INSERT INTO t2
VALUES (1, 10),
(2, 20),
(3, 30);

下列 T-SQL 批次會在兩個不同的會話中依序執行,會建立死結。

在工作階段 1 中:

BEGIN TRANSACTION xactA;

UPDATE t2
    SET b = b + 10
WHERE a = 1;

在工作階段 2 中:

BEGIN TRANSACTION xactB;

UPDATE t2
    SET b = b + 10
WHERE a = 2;

在工作階段 1 中:

UPDATE t2
    SET b = b + 100
WHERE a = 2;

在工作階段 2 中:

UPDATE t2
    SET b = b + 20
WHERE a = 1;

在此情況下,每個會話都會在自己的交易識別碼 TID 資源上持有獨佔鎖(X),並等待另一個 TID 上的共用鎖(S),導致死結。

下列縮寫死結報告包含最佳化鎖定特有的元素和屬性。 在死結報告 <resource-list>中的每一個資源下,每一個 <xactlock> 元素都會報告死結之每一個成員的基礎資源及 TID 鎖定資訊。

<deadlock>
 <victim-list>
  <victimProcess id="process12994344c58" />
 </victim-list>
 <process-list>
  <process id="process12994344c58" taskpriority="0" logused="272" waitresource="XACT: 23:2476:0 KEY: 23:72057594049593344 (8194443284a0)" waittime="447" ownerId="3234906" transactionname="xactA" lasttranstarted="2025-10-08T21:36:34.063" XDES="0x12984ba0480" lockMode="S" schedulerid="2" kpid="204928" status="suspended" spid="95" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2025-10-08T21:36:40.857" lastbatchcompleted="2025-10-08T21:36:34.063" lastattention="2025-10-08T21:36:11.340" clientapp="Microsoft SQL Server Management Studio - Query" hostname="WS1" hostpid="23380" loginname="user1" isolationlevel="read committed (2)" xactid="3234906" currentdb="23" currentdbname="AdventureWorksLT" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
   <inputbuf>
UPDATE t2
    SET b = b + 20
WHERE a = 1;
   </inputbuf>
  </process>
  <process id="process1299c969828" taskpriority="0" logused="272" waitresource="XACT: 23:2477:0 KEY: 23:72057594049593344 (61a06abd401c)" waittime="3083" ownerId="3234886" transactionname="xactB" lasttranstarted="2025-10-08T21:36:30.303" XDES="0x12995c84480" lockMode="S" schedulerid="2" kpid="63348" status="suspended" spid="88" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2025-10-08T21:36:38.223" lastbatchcompleted="2025-10-08T21:36:30.303" lastattention="1900-01-01T00:00:00.303" clientapp="Microsoft SQL Server Management Studio - Query" hostname="WS1" hostpid="23380" loginname="user1" isolationlevel="read committed (2)" xactid="3234886" currentdb="23" currentdbname="AdventureWorksLT" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
   <inputbuf>
UPDATE t2
    SET b = b + 100
WHERE a = 2;
   </inputbuf>
  </process>
 </process-list>
 <resource-list>
  <xactlock xdesIdLow="2476" xdesIdHigh="0" dbid="23" id="lock1299fa06c00" mode="X">
   <UnderlyingResource>
    <keylock hobtid="72057594049593344" dbid="23" objectname="e6fc405e-1ee8-49df-a2b3-54ee0151d851.dbo.t2" indexname="PK__t2__3BD0198ED3CBA65E" />
   </UnderlyingResource>
   <owner-list>
    <owner id="process1299c969828" mode="X" />
   </owner-list>
   <waiter-list>
    <waiter id="process12994344c58" mode="S" requestType="wait" />
   </waiter-list>
  </xactlock>
  <xactlock xdesIdLow="2477" xdesIdHigh="0" dbid="23" id="lock129940b2380" mode="X">
   <UnderlyingResource>
    <keylock hobtid="72057594049593344" dbid="23" objectname="e6fc405e-1ee8-49df-a2b3-54ee0151d851.dbo.t2" indexname="PK__t2__3BD0198ED3CBA65E" />
   </UnderlyingResource>
   <owner-list>
    <owner id="process12994344c58" mode="X" />
   </owner-list>
   <waiter-list>
    <waiter id="process1299c969828" mode="S" requestType="wait" />
   </waiter-list>
  </xactlock>
 </resource-list>
</deadlock>