不返回記錄的查詢掛在鎖上
我們注意到這種奇怪的情況,即不返回記錄的查詢將在指定時等待鎖定
WITH(UPDLOCK)
。查詢很簡單
SELECT primaryKey FROM someTable WITH(UPDLOCK) WHERE columA = 'A' AND columnB BETWEEN 6 AND 8 AND columnC != 'C' ;
執行此查詢的系統使用隔離級別read uncommitted,我們無法更改。
有沒有一種方法,例如提示,不會讓這個等待,但如果它會產生結果,仍然會鎖定記錄?
我注意到索引使用在其中起作用,因為其他查詢沒有這種行為。但遺憾的是,定義索引也不在可能的範圍內。
我想出的唯一解決方案是
IF EXISTS
在執行實際查詢之前在其中拋出一個,但由於競爭條件,它仍然可能發生。類似的問題已在此處得到解答:為什麼 UPDLOCK 會導致 SELECTs 掛起(鎖定)?
如果沒有索引來定位要鎖定的行,所有測試的行都會被鎖定,並且對符合條件的行的鎖定會一直保持到事務完成。
但我不認為那裡提供的答案適用於我的問題版本,因為查詢不會返回行來“限定”。
遺憾的是,我無法訪問該系統。我們只能對某些表執行查詢。
目的是鎖定和讀取記錄(如果存在)並最終更新它。如果失去,請插入。
UPDLOCK
之所以使用它,是因為在我的理解中,當鎖定未送出的讀取隔離時,這是正確的。該表有一個主鍵,但未在 where 子句中使用。
沒有直接的方法可以做你想做的事:在讀取未送出的隔離中讀取行,只
U
對與給定謂詞匹配的行加鎖。
UPDLOCK
工作原理當您指定 時
UPDLOCK
,SQL ServerU
在行或頁級別獲取鎖。如果需要一個表鎖,SQL Server 會X
取而代之的是一個鎖。無論是否找到任何匹配的行,表和頁鎖都會保留到事務結束。使用行級鎖時,SQL Server 總是在測試之前
U
對行進行鎖,以查看它是否匹配條件。在測試一行的特殊情況下,發現與同一數據訪問運算符中的謂詞不匹配(例如掃描或查找),目前行上的鎖在獲取下一行上的鎖
U
之前被釋放。U
否則,U
鎖會像往常一樣保持到事務結束。這是對 SQL Server 如何實現
UPDLOCK
提示的描述。我確實理解文件中並不清楚,或者您希望它如何表現。然而,事情就是這樣。解決方法
沒有完美的方法來實現您想要的,但有幾個部分解決方案。
第一個是
WITH (UPDLOCK, READPAST, ROWLOCK)
:SELECT ST.PrimaryKey FROM dbo.SomeTable AS ST WITH (UPDLOCK, READPAST, ROWLOCK) WHERE ST.ColumnA = 'A' AND ST.ColumnB BETWEEN 6 AND 8 AND ST.ColumnC != 'C';
如果要測試的行具有不兼容的行級鎖,這將跳過阻塞。如果在頁或表級別持有不兼容的鎖,查詢仍將阻塞。
第二種解決方法是在會話隔離級別讀取,然後
U
通過加入主鍵來鎖定單獨的數據訪問:SELECT CA.PrimaryKey FROM dbo.SomeTable AS ST -- No hints here CROSS APPLY ( SELECT TOP (1) ST2.PrimaryKey FROM dbo.SomeTable AS ST2 WITH (UPDLOCK, FORCESEEK) WHERE ST2.PrimaryKey = ST.PrimaryKey ) AS CA WHERE ST.ColumnA = 'A' AND ST.ColumnB BETWEEN 6 AND 8 AND ST.ColumnC != 'C';
重要的是要完全遵循那裡的模式,包括頂部。如果正確實施,此方法在實踐中應該非常可靠。
上插
我不得不提到這個問題似乎是一個 upsert 模式。
UPDLOCK
單獨服用是不夠的。您還需要圍繞 upsert 的每個部分的事務,以及
SERIALIZABLE
確保任何不存在的行在執行插入之前繼續不存在的提示。更新鎖只能對存在的行進行。請參閱Michael J Swart 的SQL Server UPSERT 模式和反模式。當然,當您使用
SERIALIZABLE
語義時,您的阻塞機會可能會大大增加。