簡體   English   中英

防止 SQL Server 中的死鎖

[英]Preventing deadlocks in SQL Server

我有一個連接到 SQL Server 2014 數據庫的應用程序,該數據庫將多行合並為一個。 應用程序運行時沒有與此數據庫的其他連接。

首先,在特定時間跨度內選擇一大塊行。 此查詢使用與集群查找合並的非集群查找(TIME 列)。

select ...
from FOO
where TIME >= @from and TIME < @to and ...

然后,我們在 c# 中處理這些行並將更改寫入單個更新和多個刪除,每個塊會發生很多次。 這些也使用非聚集索引查找。

begin tran

update FOO set ...
where NON_CLUSTERED_ID = @id

delete FOO where NON_CLUSTERED_ID in (@id1, @id2, @id3, ...)

commit

使用多個並行塊運行此程序時,我遇到了死鎖。 我嘗試使用ROWLOCK進行updatedelete ,但由於某種原因,這導致了比以前更多的死鎖,即使塊之間沒有重疊。

然后我在update上嘗試TABLOCKX, HOLDLOCK ,但這意味着我不能並行執行我的select ,所以我失去了並行性的優勢。

知道如何避免死鎖但仍然處理多個並行塊嗎?

在這種情況下,在我的select上使用NOLOCK是否安全,因為塊之間沒有行重疊? 那么TABLOCKX, HOLDLOCK只會阻止updatedelete ,對嗎?

還是我應該接受會發生死鎖並在我的應用程序中重試查詢?

UPDATE (附加信息):到目前為止,所有死鎖都發生在updatedelete階段,在select中沒有。 如果我今天不能解決這個問題,我會嘗試獲取一些死鎖日志(之前沒有啟用正確的跟蹤標志)。

更新:這些是ROWLOCK發生的兩種死鎖安排,它們都只指delete語句和它使用的非聚集索引。 我不確定這些是否與沒有任何表提示的死鎖相同,因為我無法重現其中任何一個。

死鎖 1 死鎖 2

詢問.xdl 是否還需要其他任何東西,我有點厭倦了附加整個東西。

關於死鎖的一般建議:確保您以相同的順序執行所有操作,即針對不同的進程以相同的順序獲取鎖。

您可以在 microsoft.com 上的這篇關於最小化死鎖的技術文章中找到相同的建議。 它被列在第一位是有充分理由的。

  • 以相同的順序訪問對象。
  • 避免交易中的用戶交互。
  • 保持交易簡短,一批。
  • 使用較低的隔離級別。
  • 使用基於行版本控制的隔離級別。
  • 將 READ_COMMITTED_SNAPSHOT 數據庫選項設置為 ON 以啟用已提交讀事務以使用行版本控制。
  • 使用快照隔離。
  • 使用綁定連接。

卡托提問后更新:

在這里如何以相同的順序獲取鎖? 你對他如何改變他的 SQL 來做到這一點有什么建議嗎?

無論在什么環境下,死鎖總是相同的:兩個進程(比如AB )以不同的順序獲取多個鎖(比如XY ),因此A正在等待YB正在等待XA持有X B持有Y

它適用於此處,因為DELETEUPDATE語句隱含地獲取行或索引范圍或表上的鎖(取決於引擎認為合適的內容)。

您應該分析您的流程並查看是否存在可以以不同順序獲取鎖的情況。 如果這沒有顯示任何內容,您可以使用 SQL Server Profiler 分析死鎖

要跟蹤死鎖事件,請將死鎖圖事件類添加到跟蹤中。 此事件類使用有關死鎖中涉及的進程和對象的 XML 數據填充跟蹤中的 TextData 數據列。 SQL Server Profiler 可以將 XML 文檔提取到死鎖 XML (.xdl) 文件中,稍后您可以在 SQL Server Management Studio 中查看該文件。 您可以將 SQL Server Profiler 配置為將死鎖圖事件提取到包含所有死鎖圖事件的單個文件中,或者提取到單獨的文件中。

我會在更新事務中使用sp_getapplock來防止此代碼的多個實例並行運行。 這不會像表鎖定提示那樣阻塞選擇語句。

您仍然應該編寫重試邏輯,因為獲取鎖可能需要一段時間,比超時參數更長。

這就是將更新事務包裝到sp_getapplock中的方式。

BEGIN TRANSACTION;
BEGIN TRY

    DECLARE @VarLockResult int;
    EXEC @VarLockResult = sp_getapplock
        @Resource = 'some_unique_name_app_lock',
        @LockMode = 'Exclusive',
        @LockOwner = 'Transaction',
        @LockTimeout = 60000,
        @DbPrincipal = 'public';

    IF @VarLockResult >= 0
    BEGIN
        -- Acquired the lock
        update FOO set ...
        where NON_CLUSTERED_ID = @id

        delete FOO where NON_CLUSTERED_ID in (@id1, @id2, @id3, ...)

    END ELSE BEGIN
        -- return some error code, so that the caller could retry
    END;

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    -- handle the error
END CATCH;

選擇語句不需要任何更改。

我建議不要使用NOLOCK ,即使您說塊中的 ID 不重疊。 有了這個提示,SELECT 查詢可以跳過一些正在更改的頁面,它可以讀取一些頁面兩次。 這種行為是不可能被容忍的。

請在代碼中以這種格式使用 get applock。 存儲過程 sp_getapplock 將鎖放在應用程序資源上。

EXEC Sp_getapplock @Resource = 'storeprocedurename',@LockMode = 'Exclusive',@LockOwner = 'Transaction',@LockTimeout = 25000

這是非常有幫助的。 請增加 LockTimeout 以減少死鎖

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM