Sql-Server

在這種情況下使用 WITH (NOLOCK) 安全嗎?

  • February 21, 2022

設想

我有一個處理並發 SELECT 和 DELETE 的表。我的 SELECT 語句出現了一些死鎖。我假設來自其他事務的 DELETE 正在獲得排他鎖並與 SELECT 的共享鎖衝突。

細節

  • SQL Server 14.00.3281.6.v1
  • 在 AWS RDS 上執行

案例

  • 我的應用程序可以通過多種方式觸發 SELECT。
  • 如果應用程序觸發 DELETE,它將(總是)觸發 SELECT 以檢索反映 DELETE 效果的結果。

在並發 SELECT 和 DELETE 的情況下,它可能看起來像這樣(在 MS Paint 中繪製,因為我試圖變得專業……):

時間線圖片

編輯:上面的“觸發器”並不是指實際的數據庫觸發器,而只是應用程序行為。

研究

我在 DBA Stack Exchange 上四處尋找,發現我可以SELECT myTable WITH (NOLOCK)阻止共享鎖。我正在考慮使用它,但我知道有很多警告和陷阱,所以我想驗證我的決定或在必要時更換它。

我是 WITH (NOLOCK) 的新手,所以這是我從這個有用的網站中學到的:

WITH (NOLOCK) 表提示檢索行而不等待正在讀取或修改相同數據的其他查詢完成其處理。

理由

這些引用來自同一個連結。在每一項下,我都描述了我的想法,得出的結論是這種行為不會影響我。

通常,經常使用顯式表提示被認為是您通常應該避免的不好的做法。具體針對NOLOCK表提示,讀取未送出的數據,讀完後可能回滾,可能會導致臟讀,在讀取未送出數據的過程中讀取正在修改或刪除的數據時會發生這種情況,從而使數據您閱讀的內容可能有所不同,甚至從未存在過。

臟讀:我認為我不需要關心這個,因為 SELECT 實際上並不是因為臟讀而無效。任何導致臟讀的 DELETE 都會觸發一個新的 SELECT 來糾正最終結果。 我認為兩個 SELECT 結果都是有效的,儘管一個只在幾毫秒內有效。

WITH (NOLOCK) 表提示也會導致不可重複讀取;當需要多次讀取相同的數據並且在這些讀取期間數據發生變化時,就會發生這種讀取。在這種情況下,您將讀取同一行的多個版本。

不可重複讀取:我的 SELECT 只有一個來自該表的 SELECT 語句,所以我認為這不是問題。

幻讀也可能是使用 WITH(NOLOCK) 表提示的結果,在該提示中,當插入新記錄的事務回滾時,您將獲得更多記錄,而當正在刪除現有數據的事務回滾時,您將獲得更少的記錄.

幻讀:與臟讀相同

當其他事務將您尚未讀取的數據移動​​到您已經掃描的位置,或者將新頁面添加到您已經掃描的位置時,可能會出現另一個問題。在這種情況下,您將錯過這些記錄,並且不會在返回的結果中看到它。如果另一個事務將您已經掃描的數據移動​​到您尚未讀取的新位置,您將讀取數據兩次。

我不確定這是否會影響我的案例,但我認為僅刪除幾行不會導致這種級別的頁面重新洗牌。不過,我真的不知道我在談論這個話題,所以也許我離題了。

問題

這個案例是真正可以安全使用的罕見案例之一,WITH (NOLOCK)還是我應該考慮一些危險?

初步結果

在我寫這個問題的時候,我只是把它扔到 Dev 中嘗試一下,似乎某個地方仍然發生了死鎖。我仍然想了解這種方法是否有效,以及它是否仍然是整體死鎖解決方案的一部分。

其他想法

我發現這個建議使用 SNAPSHOT 隔離級別,但我不確定當我的案例可以承受臟讀等副作用時,我是否應該承擔性能損失。

免責聲明:我不是 DBA,而是具有幾年 DB 經驗的軟體開發人員。我只是“教科書級別”熟悉鎖定、頁面、提示等的內部工作原理,所以請多多包涵,如果我能以任何方式改進問題,請告訴我。如果需要,我很高興澄清。

我不是 DBA,而是具有幾年 DB 經驗的軟體開發人員。我只是“教科書級別”熟悉鎖定、頁面、提示的內部工作原理,

然後你應該使用 SNAPSHOT 隔離,或者將你的數據庫設置為 READ COMMITTED SNAPSHOT,因為從根本上來說,編寫正確的、可擴展的、無死鎖的程式碼更簡單。

NOLOCK/ReadUncommited 以可預測的方式放鬆並發模型是一個常見的誤解。IE,它只是允許您在最終可能回滾的狀態下讀取數據。不是這種情況。行在更改時有時需要移動,NOLOCK 查詢可能會錯過這些行或多次讀取它們。或者,當一個被更新但另一個沒有被更新時,一個 NOLOCK 查詢可能會讀取一個非聚集索引和基礎表。 這兩者都可能導致錯誤的結果。

READ COMMITTED SNAPSHOT/SNAPSHOT 的代價是行需要一些額外的簿記。在更新和刪除的行中添加了一個額外的 14 字節欄位以指向先前的行版本,並且行版本儲存在 TempDb 或使用者數據庫中。但也有性能優勢。您的工作負載可以更輕鬆地擴展,因為鎖定爭用更少,並且會話能夠更多地並發執行。

引用自:https://dba.stackexchange.com/questions/281560