初步了解更新鎖(U)與排它鎖(X)
一直沒有認真了解UPDATE操作的鎖。近期在MSDN論壇上看到一個問題,詢問堆表更新的死鎖問題,問題非常easy,有相似這種表及數據:
CREATE TABLE dbo.tb(
c1 int,
c2 char(10),
c3 varchar(10)
);
GO
DECLARE @id int;
SET @id = 0;
WHILE @id <5
BEGIN;
SET @id = @id + 1;
INSERT dbo.tb VALUES( @id, ‘b‘ + RIGHT(10000 + @id, 4), ‘c‘ + RIGHT(100000 + @id, 4)
END;
在查詢一中運行更新操作:
BEGIN TRAN
UPDATE dbo.tb SET c2 = ‘xx‘ WHERE c1 = 2;
WAITFOR DELAY ‘00:00:30‘;
UPDATE dbo.tb SET c2 = ‘xx‘ WHERE c1 = 5;
ROLLBACK;
在查詢一運行開始後,立即在查詢二中運行以下的操作
BEGIN TRAN
UPDATE dbo.tb SET c2 = ‘xx‘ WHERE c1 = 1;
ROLLBACK;
為什麽會出現死鎖,假設條件改為 c1 = 4 則不會死鎖。
開始的時候想得比較簡單,死鎖的表現是形成循環等待(對於兩個查詢而言,能夠簡單地覺得就是在相互等待對方鎖定資源的釋放)
對於這個樣例而言。第一個查詢更新兩次,會先更新並鎖定一條記錄,然後等待第二個更新。但第二個查詢僅僅會更新一條記錄。它要麽與第一個查詢沖突,無法獲得鎖。須要等待查詢一完畢,這個時候它並沒有鎖定什麽;要麽能夠獲得鎖,完畢更新。
似乎不應該會出現死鎖,死鎖會不會是其它原因導致。
在自己的電腦上簡單測試了一下。似乎也確實沒有死鎖。
但後面通過Profile跟蹤更新操作的下鎖情況才發現。自己的分析大錯特錯了。
主要原因在於沒有正確理解更新操作是怎樣用鎖的。
在聯機幫助上“鎖模式”中有關於更新的U(更新鎖)和X(排它鎖)的說明
http://msdn.microsoft.com/zh-cn/library/ms175519(v=sql.105).aspx
只是說得確實挺模糊的。裏面還提到了S鎖。我一直以為是查詢數據過程中用的S鎖(也 SELECT 一樣)。找到滿足條件的記錄後用U鎖,再轉換為X鎖做更新。
Profile(事件探查器)跟蹤的結果讓我知道了這是一個錯誤的理解,在Profile中新建一個跟蹤,選擇Locks中的Lock:Acquired (加鎖),Lock:Acquired(釋放鎖)解兩個事件,在篩選中設置僅僅跟蹤測試用的查詢窗體相應的spid(能夠運行 PRINT @@SPID 獲得),然後運行一個更新語句。比方 UPDATE dbo.tb SET c2 = ‘xx‘ WHERE c1 = 3
在Profile中能夠看到。對於每條記錄都有加 U 鎖的操作,對於不滿足條件的記錄,會立即釋放U鎖;對於滿足條件的記錄,終於轉換為X鎖。例如以下圖所看到的。
註意一下,在這個跟蹤結果裏面。並沒有出現S鎖。
另外學做了一些測試:
通過加大記錄量做更新測試,會發現數據掃描涉及的記錄都有U鎖,並不限於更新記錄所在的頁。這從還有一個角度說明了大表中Scan 可怕。
當使用索引Scan的時候,也會通過跟蹤發現所Scan的索引資源有U鎖,假設更新不涉及索引變化。那以僅僅會相應的記錄有U轉X鎖。索引的U鎖會釋放;假設影響索引,那麽索引的U鎖會轉X鎖。
刪除操作與更新操作相似
使用 UPDATE aSET c2 = ‘xx‘ FROM dbo.tb AS a WITH(NOLOCK) WHERE c1 = 3 的加鎖情況是一樣的。 並不會由於NOLOCK的提示而不加 U 或者 X 鎖
最後回頭研究一下演示樣例中的死鎖問題:
對於查詢一,第一個更新依次掃描表中全部記錄,對於每條記錄,加 U 鎖,推斷是否符合更新條件。假設符合,轉換為 X 鎖;假設不符合條件。釋放 U 鎖。第一個更新完畢的時候,查詢一鎖定了一條記錄(由於事務未完畢,所以鎖一直保持),然後等待第二個更新
對於查詢二,依次掃描表中的每條記錄(與前面的更新一樣),假設它更新的記錄在查詢一更新的記錄前被掃描到,那麽這條記錄也會變成 X 鎖;當繼續並進行到查詢一的X鎖記錄的零點,U 與 X 沖突,無法繼續,這時候查詢二等待查詢一釋放鎖
查詢一的第二個更新開始運行。依次掃描每條記錄。同一個事務內不會有沖突。所以它不會與自己之前鎖定的記錄有沖突,但進行到查詢二鎖定的記錄的時候,它也無法獲得 U 鎖。它須要等待查詢二釋放資源。
這個時候就形成了相互等待,符合死鎖條件
假設查詢二須要更新的記錄在查詢一的第一個更新記錄之後。則不會有死鎖。由於查詢二在掃描到查詢一第一個更新的記錄時就會由於鎖沖突等待了,這個時候它沒有對不論什麽記錄設置與查詢一的操作有沖突的鎖。我自己測試的時候沒有死鎖,就是這種情況。
註意這裏面提到的順序。是數據讀取的順序,不一定與存儲順序一樣,磁盤上記錄的順序也不一定與INSERT的記錄順序一樣,這也是我用相同條件沒有測試出死鎖的原因(我的環境中,恰好讀出的順序與INSERT的順序不一樣)
更新時,記錄讀取的順序,能夠通過Profile跟蹤的Lock:Acquired (加鎖)事件來看。涉及大量數據時,假設server支持。還會有並發讀取。這也是分析死鎖時要考慮的因素
初步了解更新鎖(U)與排它鎖(X)