前言
對於上述鎖其實是一個老生常談的話題了,但是我們是否能夠很明確的知道在什么情況下會存在上述各種鎖類型呢,本節作為SQL Server系列末篇我們 來詳細講解下。
Range-Lock
上述關於RangeS-U、RangeS-S、RnageX-X以及還有RangeI-N這四種鎖屬於范圍鎖(Range-Lock)范疇。那么在什么情況下會存在范圍鎖呢,當在SERIALIZABLE最高隔離級別時范圍鎖將會被用到,這也就意味着直到事務開啟到結束查詢出的結果集是一致的以此來防止幻影。在該隔離級別中鎖定的數據集合基於覆蓋了所查詢出的行的索引的鍵值范圍,以此來確保鎖定的范圍的值不會被修改或者其他並發事務不會為相同的值范圍插入新值,任何其他事務對范圍內數據的修改、添加和刪除都需要修改索引,所以此時將會被阻塞,因為范圍鎖覆蓋了索引條目。下面我們一個個來分析何時出現哪種類型的范圍鎖。
RangeS-S
首先我們創建測試表
CREATE TABLE RangeLock (RId int NOT NULL IDENTITY (1, 1) PRIMARY KEY, Rname nvarchar (20), SName nvarchar (20))
接下來插入測試數據:
INSERT INTO [dbo].[RangeLock] ([RName]) VALUES ('anna'), ('antony'), ('angel'), ('ARLEN'), ('BENEDICT'), ('BILL'), ('BRYCE'), ('CAROL'), ('CEDRIC'), ('CLINT'), ('DARELL'), ('DAVID')
接下來我們設置最高隔離級別並開啟事務查詢,如下:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO Begin Transaction GO SELECT RId FROM dbo.RangeLock WHERE RId = 1
我們看下此時的查詢執行計划。
至於為何為聚集索引查找不用我再多講了,此時會話Id為51接下來我們來檢查鎖。
sp_lock 51
什么情況怎么沒有看見范圍鎖呢? 從上我們可以看出索引Id=1鎖定的鍵的鎖模式是正常的共享鎖模式,並未出現我們所講的在最高隔離級別下出現的范圍鎖更不用說RangeS-S范圍鎖了。別着急我們對RName建立一個非聚集索引看看。
我們再來查詢RName的值,如下:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO BEGIN TRAN GO SELECT RName FROM dbo.RangeLock WHERE RName = 'anna'
此時再來看看查詢計划。
此時我們來分析分析上述圖,對照我們根據默認創建的聚集索引去查詢數據此時並未出現RangeS-S范圍鎖,而當對RName創建非聚集索引時此時出現了RangeS-S范圍鎖,同時上圖顯示此時有兩個范圍鎖,我們明明查詢一行數據為何會出現兩個范圍鎖呢,這個時候我們要從最高級別所解決的問題講起,SERIALIZABLE最高級別是為了解決幻影讀取的問題也就是前后查詢數據不一致問題,因為有了范圍鎖的出現,它會鎖住我們所查詢的那一行數據以及下一行數據,這樣就確保了其他事務無法對當前事務中數據以及下一行數據進行更新、插入和刪除。為了驗證這一點我們在開啟一個會話來刪除上述查詢數據的下一行數據
DELETE FROM dbo.RangeLock WHERE RName = 'antony'
由上驗證了我的觀點,說了這么多范圍鎖肯定是有范圍的,到這里我們來對范圍鎖下一個結論。
對於等值條件,如果所查詢鍵存在且索引為非唯一索引此時才出現范圍鎖,在非唯一索引中鎖定的是請求的鍵和該鍵的下一條,如果下一條數據不存在將無限擴充來進行范圍限定,如果索引為聚集索引則呈現出的是常規的共享鎖即使是我們設置最高隔離級別。
上述還結論還未完成,為了深刻討論我們重新創建測試表和創建非聚集索引以便更清楚的認識到范圍鎖。
CREATE TABLE RangeLock (RId int NOT NULL IDENTITY (1, 1), RName nvarchar (20), SName nvarchar (20)) ALTER TABLE RangeLock ADD PRIMARY KEY (RId); CREATE NONCLUSTERED INDEX [ix_rname] ON [dbo].[RangeLock] ( [RName] ASC )
再插入上述測試表,我們再來查詢該表。
SELECT * FROM dbo.RangeLock ORDER BY Rname
上述情況是查詢的篩選條件即鍵是存在的,如果鍵不存在又當如何呢?
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO BEGIN TRAN GO SELECT RName FROM dbo.RangeLock WHERE RName = '鍵不存在'
看出什么沒有對於RName的索引Id等於2的情況依然存在范圍鎖,這種情況該如何解釋呢,我們最終得出如下結論。
對於等值條件,如果所查詢鍵存在且索引為非唯一索引此時才出現范圍鎖,在非唯一索引中鎖定的是請求的鍵和該鍵的下一條,如果下一條數據不存在將無限擴充來進行范圍限定,如果索引為聚集索引則呈現出的是常規的共享鎖即使是我們設置最高隔離級別,如果查詢鍵不存在那么無論是聚集索引還是非聚集索引,此時范圍鎖將鎖住下一個鍵,如果下一個鍵不存在,那么將無限擴充來進行范圍鎖定。
RangeS-U
分析完RangeS-S接下來我們分析RangeS-U范圍鎖。在什么情況下會出現RangeS-U范圍鎖呢,要出現RangeS-U必須滿足以下三個條件。
(1)事務中必須包含一條更新語句。
(2)在WHERE上指定一個篩選條件並且限制更新行數,同時條件至少有一個來自於索引,也就是說定義了來自於索引的鍵值范圍
(3)更新列不能包含索引定義。
我們直接看上述已經創建的測試表,並作如下更新。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO BEGIN TRAN GO UPDATE dbo.RangeLock SET SName ='surname' WHERE Rname BETWEEN 'anna' AND 'arlen'
此時我們來看看查詢執行計划。
此時我們看到的聚集索引更新和在列RName上的非聚集索引查找,也就說定義了索引查找的范圍。
由上我們看到三行在主鍵上的排它鎖來用於主鍵更新,因為我們的值在anna和arlen之間,在表中存在三行數據,但是在IndId = 2獲得在ix_rname上的RangeS-U范圍鎖卻有四行,讓我們驚訝不已,但是在表中實際上只有三行,這又是為何,說明第四行鎖的鍵在BARRY上。不行我們對第四行進行更新試試看。
begin transaction UPDATE dbo.RangeLock SET RName = 'Jeffcky' WHERE RName = 'barry'
由上證明了我的觀點,此時雖然查詢的是三行的數據,但是實際上RangeS-U范圍鎖鎖定的不僅僅是三行還有下一行數據。我們保持第四行阻塞,在查詢中來看看二者進程中的鎖情況。
sp_lock 54 GO sp_lock 57 GO
我們能夠看到對於更新BARRY的第四行數據此時處於WAIT狀態即阻塞。基於上述討論我們得出如下結論:
對於范圍條件,無論是聚集索引還是非聚集索引在該范圍的所有鍵都會被范圍鎖鎖定,同時也會鎖定下一個鍵即超出更新或者查詢范圍,這樣就確保了在所請求的鍵和下一行數據之間都不能插入新行,如果下一個鍵不存在則無限擴充來進行范圍鎖定。
對於RangeS-U范圍鎖意味着其他事務對於相同行的查詢是被允許的,若其他事務對范圍鍵中值進行更新則會出現阻塞,直到當前事務被提交或回滾才被允許。
RangeX-X
從字面意思理解這個就是排他范圍鎖了,當一個事務更新一個有索引的鍵時,它會獲取索引鍵上的排他鎖特定的范圍,此時則意味着其他事務對該鍵執行的添加、刪除或者修改都會被阻塞直到當前事務完成。我們看看如下例子:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO BEGIN TRAN GO UPDATE dbo.RangeLock SET RName ='ana' WHERE RName = 'anna'
我們看到一個聚集索引更新和對ix_rname上的索引查找定義了對於要查找的鍵范圍。初始一看有點像上述RangeS-U范圍鎖,此時我們將鼠標放在聚集索引更新上看到如下所示:
我們看到上述有兩個索引,一個是主鍵的聚集索引,一個是ix_rname上的非聚集索引,聚集索引基於表中的主鍵對數據行進行排序,當然此時表中的所有數據行也可以稱為葉子節點,因為聚集索引葉子節點存的就是實際數據行,而主鍵值包含了指向所有行的指針,所以對除主鍵列以外的數據行進行更新都將導致聚集索引上的葉子節點進行對應的更新。我們看看此時鎖的類型:
由於需要對索引進行更新此時在主鍵上有一個排它鎖,同時呢,我們看到對於ix_rname的索引上鍵保持了兩種范圍鎖,一種是RangeS-U,另外一種則是RangeX-X。由於此時對anna采取了RangeX-X排他范圍鎖,那么存在的RangeS-U更新范圍鎖根據我們之前的經驗來猜測此時RangeS-U對應的是RName = anna的下一行,我們來證明下:
BEGIN TRANSACTION SELECT RName FROM dbo.RangeLock WHERE RName = 'antony'
不要驚訝上述為什么會查詢出數據而不會更新,根據我們的假設此時antony對應的是RangeS-U更新范圍鎖的話,那么此時查詢相同的行絕對不會出現阻塞,下面我們來看看在另外一個會話中來更新:
BEGIN TRANSACTION UPDATE dbo.RangeLock SET RName = 'Jeffcky' WHERE RName = 'antony'
此時我們再來對比此時兩個會話中的鎖情況。
sp_lock 54 GO sp_lock 56 GO
我們馬上能看到對於相同的數據行,第二個會話進行數據行的更新此時將被RangeS-U更新范圍鎖所鎖定導致等待阻塞。至此我們對RangeX-X來下個結論:
對於RangeX-X排他范圍鎖,它僅僅只鎖定實際的修改列,對於修改的下一列將不再鎖定而是利用RangeS-U更新范圍鎖來鎖定以便在事務提交之前來維護數據的完整性直到事務提交。
總結
對於SERIALIZABLE最高隔離級別中的四種范圍鎖類型的分析和窺探,想必我們大概了解了為什么它會防止幻影,比如典型的當第一次查詢數據行為空,而第二次查詢數據行也是為空,不會出行幻影的情況則是利用RangeS-S范圍鎖,因為查詢的鍵根本不存在那么將導致范圍鎖定進行無限擴充從而導致整個表被鎖定,最終多次查詢的數據行必定一致而不是幻影。至此關於SQL Server基礎系列到這里接近尾聲,在這里為了閱讀者看起來方便列出SQL Server系列所有文章以便查閱。
http://www.cnblogs.com/CreateMyself/p/6099560.html(SQL Server-語句類別、數據庫范式、系統數據庫組成(一))
http://www.cnblogs.com/CreateMyself/p/6104345.html(SQL Server-數據庫架構和對象、定義數據完整性(二))
http://www.cnblogs.com/CreateMyself/p/6107209.html(SQL Server-簡單查詢語句,疑惑篇(三))
http://www.cnblogs.com/CreateMyself/p/6115160.html(SQL Server-聚焦聚集索引對非聚集索引的影響(四))
http://www.cnblogs.com/CreateMyself/p/6115736.html(SQL Server-聚焦使用索引和查詢執行計划(五))
http://www.cnblogs.com/CreateMyself/p/6123586.html(SQL Server-數據類型(七))
http://www.cnblogs.com/CreateMyself/p/6127186.html(SQL Server-分頁方式、ISNULL與COALESCE性能分析(八))
http://www.cnblogs.com/CreateMyself/p/6127830.html(SQL Server-聚焦強制索引查詢條件和Columnstore Index(九))
http://www.cnblogs.com/CreateMyself/p/6129924.html(SQL Server-聚焦過濾索引提高查詢性能(十))
http://www.cnblogs.com/CreateMyself/p/6138996.html(SQL Server-簡單查詢示例(十一))
http://www.cnblogs.com/CreateMyself/p/6142275.html(SQL Server-交叉聯接、內部聯接基礎回顧(十二))
http://www.cnblogs.com/CreateMyself/p/6146909.html(SQL Server-外部聯接基礎回顧(十三))
http://www.cnblogs.com/CreateMyself/p/6147883.html(SQL Server-聚焦INNER JOIN AND IN性能分析(十四))
http://www.cnblogs.com/CreateMyself/p/6154688.html(SQL Server-聚焦NOT EXISTS AND NOT IN性能分析(十五))
http://www.cnblogs.com/CreateMyself/p/6155480.html(SQL Server-聚焦EXISTS AND IN性能分析(十六))
http://www.cnblogs.com/CreateMyself/p/6165982.html(SQL Server-聚焦IN VS EXISTS VS JOIN性能分析(十九))
http://www.cnblogs.com/CreateMyself/p/6183179.html(SQL Server-聚焦計算列持久化(二十一))
http://www.cnblogs.com/CreateMyself/p/6184749.html(SQL Server-聚焦計算列或計算列持久化查詢性能(二十二))
http://www.cnblogs.com/CreateMyself/p/6185286.html(SQL Server-聚焦UNIOL ALL/UNION查詢(二十三))
http://www.cnblogs.com/CreateMyself/p/6188447.html(SQL Server-表表達式基礎回顧(二十四))
http://www.cnblogs.com/CreateMyself/p/6189548.html(SQL Server-聚焦使用視圖若干限制/建議、視圖查詢性能問題,你懵逼了?(二十五))
http://www.cnblogs.com/CreateMyself/p/6193163.html(SQL Server-聚焦在視圖和UDF中使用SCHEMABINDING(二十六))
http://www.cnblogs.com/CreateMyself/p/6193183.html(SQL Server-聚焦APPLY運算符(二十七))
http://www.cnblogs.com/CreateMyself/p/6347895.html(SQL Server-索引故事的遙遠由來,原來是這樣的?(二十八))
http://www.cnblogs.com/CreateMyself/p/6352167.html(SQL Server-聚焦事務、隔離級別詳解(二十九))
http://www.cnblogs.com/CreateMyself/p/6361825.html(SQL Server-聚焦SNAPSHOT基於行版本隔離級別詳解(三十))
http://www.cnblogs.com/CreateMyself/p/6361825.html(SQL Server-聚焦SNAPSHOT基於行版本隔離級別詳解(三十))
http://www.cnblogs.com/CreateMyself/p/6395670.html(SQL Server-聚焦事務對本地變量、臨時表、表變量影響以及日志文件存滿時如何收縮(三十一))
http://www.cnblogs.com/CreateMyself/p/6411676.html(SQL Server-聚焦深入理解動態SQL查詢(三十二))
http://www.cnblogs.com/CreateMyself/p/6362904.html(SQL Server-聚焦深入理解死鎖以及避免死鎖建議(三十三))
路漫漫其修遠兮,吾將上下而求索,不求一時的安逸,遠離舒適,遠離安逸,為可能存在的價值創造最大的可能性。