[英]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
進行update
和delete
,但由於某種原因,這導致了比以前更多的死鎖,即使塊之間沒有重疊。
然后我在update
上嘗試TABLOCKX, HOLDLOCK
,但這意味着我不能並行執行我的select
,所以我失去了並行性的優勢。
知道如何避免死鎖但仍然處理多個並行塊嗎?
在這種情況下,在我的select
上使用NOLOCK
是否安全,因為塊之間沒有行重疊? 那么TABLOCKX, HOLDLOCK
只會阻止update
和delete
,對嗎?
還是我應該接受會發生死鎖並在我的應用程序中重試查詢?
UPDATE (附加信息):到目前為止,所有死鎖都發生在update
和delete
階段,在select
中沒有。 如果我今天不能解決這個問題,我會嘗試獲取一些死鎖日志(之前沒有啟用正確的跟蹤標志)。
更新:這些是ROWLOCK
發生的兩種死鎖安排,它們都只指delete
語句和它使用的非聚集索引。 我不確定這些是否與沒有任何表提示的死鎖相同,因為我無法重現其中任何一個。
詢問.xdl 是否還需要其他任何東西,我有點厭倦了附加整個東西。
關於死鎖的一般建議:確保您以相同的順序執行所有操作,即針對不同的進程以相同的順序獲取鎖。
您可以在 microsoft.com 上的這篇關於最小化死鎖的技術文章中找到相同的建議。 它被列在第一位是有充分理由的。
- 以相同的順序訪問對象。
- 避免交易中的用戶交互。
- 保持交易簡短,一批。
- 使用較低的隔離級別。
- 使用基於行版本控制的隔離級別。
- 將 READ_COMMITTED_SNAPSHOT 數據庫選項設置為 ON 以啟用已提交讀事務以使用行版本控制。
- 使用快照隔離。
- 使用綁定連接。
卡托提問后更新:
在這里如何以相同的順序獲取鎖? 你對他如何改變他的 SQL 來做到這一點有什么建議嗎?
無論在什么環境下,死鎖總是相同的:兩個進程(比如A
和B
)以不同的順序獲取多個鎖(比如X
和Y
),因此A
正在等待Y
而B
正在等待X
而A
持有X
B
持有Y
。
它適用於此處,因為DELETE
和UPDATE
語句隱含地獲取行或索引范圍或表上的鎖(取決於引擎認為合適的內容)。
您應該分析您的流程並查看是否存在可以以不同順序獲取鎖的情況。 如果這沒有顯示任何內容,您可以使用 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.