浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题
?
本文出處:http://www.cnblogs.com/wy123/p/7501261.html?
(保留出處并非什么原創(chuàng)作品權(quán)利,本人拙作還遠(yuǎn)遠(yuǎn)達(dá)不到,僅僅是為了鏈接到原文,因?yàn)楹罄m(xù)對(duì)可能存在的一些錯(cuò)誤進(jìn)行修正或補(bǔ)充,無(wú)他)
?
?
數(shù)據(jù)庫(kù)在處理并發(fā)事物的過(guò)程中,在不同的隔離級(jí)別下有不同的鎖表現(xiàn),在非可序列化隔離級(jí)別下,存在著臟讀,不可重復(fù)讀,丟失更新,幻讀等情況。
本文不討論臟讀和不可重復(fù)讀以及丟失更新的情形,僅討論幻讀,幻讀是指在一個(gè)事物中,同一個(gè)條件,存在兩次讀到的數(shù)據(jù)行數(shù)不一致的情況。
最高隔離級(jí)別也即可序列化隔離級(jí)別消除了幻讀,幻讀的消除過(guò)程中會(huì)通過(guò)Range鎖(也即范圍鎖)來(lái)實(shí)現(xiàn)事物隔離的。
那么,Range鎖是如何產(chǎn)生的?產(chǎn)生Range鎖時(shí),鎖定的范圍又是如何確定的?不同的索引產(chǎn)生的Range鎖范圍有什么區(qū)別?
本文將對(duì)此進(jìn)行一個(gè)粗淺的分析與推斷。
查閱了很多資料,尚未得到一個(gè)非常清晰的答案,原因在于:
1,沒(méi)有指明Range鎖的范圍,觀察鎖的時(shí)候看到Range鎖產(chǎn)生之后就收?qǐng)?#xff0c;并沒(méi)有分析Range鎖產(chǎn)生時(shí),鎖定的具體范圍是什么,鎖定已存在的值沒(méi)問(wèn)題,是否鎖定未存在的值?
1,非唯一索引與唯一索引的情況下產(chǎn)生的范圍鎖,鎖定的范圍包不包括臨界值 ?
2,對(duì)于查詢表中不存在的key值(分兩種,一種是介于表中最大與最小Key之間,一種是位于最大或者最小key值之外),鎖定的范圍到底是怎么樣的?
?
測(cè)試中發(fā)現(xiàn)一個(gè)有意思的問(wèn)題,對(duì)于唯一索引,當(dāng)鎖定目標(biāo)是一個(gè)表中已存在的Key值的時(shí)候,表面上產(chǎn)生的是一個(gè)key鎖,真的就僅僅鎖定了當(dāng)前的這一個(gè)Key(數(shù)據(jù)行)嗎?
同時(shí),對(duì)于那個(gè)經(jīng)典的“并發(fā)情況下存在則更新,不存在的插入”的處理,其背后的原理,也可以用Range鎖來(lái)解釋。
?
說(shuō)明一下本文測(cè)試的原則:
1,測(cè)試均在可序列化隔離級(jí)別下測(cè)試(set transaction isolation level serializable?)。
2,測(cè)試的原則是,Session1中采用排它鎖的方式加鎖,利用共享鎖與排它鎖不兼容的特點(diǎn),Session2中采用共享鎖的方式來(lái)不斷探測(cè)Session1中產(chǎn)生的鎖的范圍。
3,測(cè)試數(shù)據(jù)庫(kù)是SQL Server 2014
?
1,測(cè)試環(huán)境構(gòu)建
1.1 ?新建測(cè)試表并寫(xiě)入數(shù)據(jù)
create table TestLock (Id int,Name varchar(100) )create clustered index idx_id on TestLock(id)insert into TestLock values (10,'aaa') insert into TestLock values (20,'bbb') insert into TestLock values (30,'ccc') insert into TestLock values (40,'ddd') insert into TestLock values (50,'eee')1.2 測(cè)試表中的數(shù)據(jù)行存儲(chǔ)位置分析?
通過(guò)系統(tǒng)命令或者表查詢測(cè)試表的page信息
--查看數(shù)據(jù)頁(yè)信息 dbcc ind('Test','TestLock',-1) --或者查詢系統(tǒng)DMV select * from sys.dm_db_database_page_allocations(db_id('Test'),object_id('TestLock'),null,null,'detailed')表TestLock的數(shù)據(jù)頁(yè)面為147
1.3 查詢147號(hào)頁(yè)面的數(shù)據(jù)行的KeyHashValue(可以認(rèn)為是數(shù)據(jù)行的唯一標(biāo)識(shí))
DBCC TRACEON(3604) DBCC PAGE(Test,1,147,3)? 這里找到數(shù)據(jù)行對(duì)應(yīng)的KeyHashValue如下圖所示
整理出來(lái)的數(shù)據(jù)行Id與其對(duì)應(yīng)的KeyHashValue如下
10:241332e1ddb0
20:69c872e07e60
30:0bdec3f2b948
40:199f61d4d268
50:0878442f3a75
?
2,Range鎖產(chǎn)生時(shí),鎖定的范圍初步分析
2.1 Range鎖產(chǎn)生的場(chǎng)景分析
在可序列化隔離級(jí)別下,測(cè)試一個(gè)Range鎖產(chǎn)生的情況
如代碼中的備注所示,第一個(gè)Session中執(zhí)行如下查詢,暫不提交事物
? ?第一個(gè)Session中執(zhí)行情況先保持(不提交也不回滾),另開(kāi)一個(gè)查詢窗口,也即第二個(gè)Session中查詢產(chǎn)生的Range鎖
可以清楚地看到產(chǎn)生兩個(gè)Range鎖的resource_description分別是0bdec3f2b948和199f61d4d268
對(duì)照上面分析出來(lái)的數(shù)據(jù)行與KeyHashValue的關(guān)系,說(shuō)明這個(gè)兩個(gè)resource_description的值分別是30和40
最重要的問(wèn)題就在這里,Range鎖的resource_description是0bdec3f2b948和199f61d4d268,既然是RangeX-X,也就是范圍鎖,那么這兩個(gè)Range鎖定的范圍是多大?
這里先給出結(jié)論,當(dāng)在產(chǎn)生key類(lèi)型的Range鎖的時(shí)候,
以上述測(cè)試case為例,每一個(gè)Range鎖對(duì)應(yīng)的范圍如下(以下表格內(nèi)容都包括臨界值,臨界值跟索引是否唯一也有關(guān),下文會(huì)有說(shuō)明)
以上述測(cè)試為例,產(chǎn)生了兩個(gè)RangeX-X類(lèi)型的Key類(lèi)型鎖,分別是Id為30和40對(duì)應(yīng)的RangeX-X,那么鎖定的范圍就是20~40,
既然是一個(gè)范圍鎖,就跟表中是區(qū)間的數(shù)據(jù)是否存在無(wú)關(guān)。
上面的話怎么理解?
如何證明鎖定的范圍就是20~40,看以下測(cè)試:
?
2.2查詢被鎖定區(qū)間的值,不管這個(gè)值是否已經(jīng)存在于表中,都是會(huì)被被阻塞的
Session2中以序列化隔離級(jí)別執(zhí)行如下代碼,
查詢Id = 35的Id值,雖然Id = 35是一個(gè)不存在的值,但是這個(gè)區(qū)間被鎖定了,按道理,查詢Id = 35的查詢是會(huì)被阻塞的。
測(cè)試正如所預(yù)料的,因?yàn)檫@個(gè)區(qū)間被鎖定了(排它鎖),查詢這個(gè)區(qū)間的任何一個(gè)值都被阻塞,而不管查詢的Id值是否存在
繼續(xù)測(cè)試,回滾Session2中的查詢,查詢一個(gè)下限范圍的Id,
同樣的道理,雖然Id = 25是一個(gè)不存在的值,但是這個(gè)區(qū)間被鎖定了,按道理,查詢Id = 25的查詢是會(huì)被阻塞的。
也是正如所預(yù)料的,因?yàn)檫@個(gè)區(qū)間被鎖定了(排它鎖),查詢這個(gè)區(qū)間的任何一個(gè)值都被阻塞,而不管查詢的Id值是否存在
2.3查詢非鎖定區(qū)間的值,不管這個(gè)值是否已經(jīng)存在于表中,都是不會(huì)被被阻塞的
上面說(shuō)了,鎖定的范圍就是20~40,那么查詢一個(gè)非此區(qū)間的Id,是不會(huì)被鎖定的。
繼續(xù)測(cè)試,回滾Session2的查詢,查詢一個(gè)Id = 50的值,在非鎖定范圍之內(nèi)(也即非20~40這個(gè)區(qū)間的Id),是可以正常查詢的,也是預(yù)期的。
繼續(xù)回滾Session2中的查詢,查詢一個(gè)小于20且存在的Id值,查詢成功
繼續(xù)回滾Session2中的查詢,查詢一個(gè)小于20且不存在的Id值,這里使用Id = 15,查詢成功
?
以上測(cè)試可以說(shuō)明,一個(gè)Key類(lèi)型的Range鎖,都對(duì)應(yīng)一個(gè)范圍,加鎖的時(shí)候鎖定的是一個(gè)范圍,對(duì)于鎖定范區(qū)間的值,不管是否存在,都是會(huì)被阻塞的,而不僅僅是鎖定已有數(shù)據(jù)行的作用。
3,非唯一索引情況下,范圍鎖鎖定的范圍分析
? 那么,一個(gè)Key類(lèi)型的Range鎖究竟鎖定的范圍是多大?
這也是一個(gè)非常有意思的問(wèn)題,這里同樣先給出結(jié)論,分為以下幾種情況:
?
3.1 如果鎖定的目標(biāo)Id的值存在與表中,且大于表中的最大值,小于表中的最小值,那么鎖定的區(qū)間就是小于鎖定目標(biāo)的第一個(gè)最大值,大于鎖定目標(biāo)的第一個(gè)最小值這個(gè)區(qū)間。
上述測(cè)試已經(jīng)說(shuō)明了這個(gè)鎖的區(qū)間
比如上述測(cè)試鎖定的目標(biāo)值,在Session1中以xlock的方式鎖定Id =30,產(chǎn)生的范圍鎖,鎖定的范圍是下限值是20(小于30的最大值),上限值是40(大于30的最小值)
文字說(shuō)起來(lái)有點(diǎn)繞,畫(huà)個(gè)圖看起來(lái)就直觀了,如下
鎖定的目標(biāo)是30,因?yàn)樵阪i定30的時(shí)候會(huì)產(chǎn)生范圍鎖,這個(gè)范圍鎖鎖定的區(qū)間是20~40
3.2 如果鎖定的目標(biāo)Id的值不存在與表中,且大于表中的最大值,小于表中的最小值,那么鎖定的區(qū)間就是小于鎖定目標(biāo)的第一個(gè)最大值,大于鎖定目標(biāo)的第一個(gè)最小值這個(gè)區(qū)間。
? 重新開(kāi)始測(cè)試,Session1和Session2中都回滾之前的測(cè)試
在Session1中執(zhí)行一個(gè)Id = 35的查詢,這個(gè)查詢是添加了排它鎖的方式執(zhí)行的,這個(gè)Id是不存在的。
在Session2中觀察產(chǎn)生的鎖,會(huì)發(fā)現(xiàn)有一個(gè)resource_description是199f61d4d268的范圍鎖?。 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
KeyHashValue為199f61d4d268的Id是40,結(jié)合上述列表,40這個(gè)Id對(duì)應(yīng)的鎖的范圍是30~40
?
那么究竟鎖定的范圍是不是30~40,同樣可以在Session2中用共享鎖查詢的方式來(lái)探測(cè)Session1中鎖定的范圍
測(cè)試1,查詢Id = 31的值,被鎖定
測(cè)試2,查詢Id=39的值,被鎖定
測(cè)試3,查詢Id = 29得值,位于鎖定區(qū)間之外,查詢成功,盡管這是一個(gè)不存在的值,但是在鎖定區(qū)間之外,可以查詢成功。
測(cè)試4,查詢Id = 50的值,位于鎖定區(qū)間之外,查詢成功,這是一個(gè)存在的Id值
當(dāng)鎖定的目標(biāo)在表中不存在的時(shí)候,且鎖定目標(biāo)大于表中已存在的最小Id值,小于最大Id值,
那么鎖定的區(qū)間就是小于鎖定目標(biāo)的第一個(gè)最大值,大于鎖定目標(biāo)的第一個(gè)最小值這個(gè)區(qū)間。
同理,當(dāng)產(chǎn)生范圍鎖的時(shí)候,鎖定的是一個(gè)區(qū)間,而不管這個(gè)區(qū)間是否存在值,或者存在多少個(gè)值。
同樣用一個(gè)圖來(lái)表示,看起來(lái)更直觀一點(diǎn)
?
3.3 如果鎖定的目標(biāo)Id的值不存在與表中,且大于表中的最大值 ,鎖定的范圍是一個(gè)表中最大值到無(wú)窮大的一個(gè)范圍
? 重新開(kāi)始測(cè)試,Session1和Session2中都回滾之前的測(cè)試
在Session1中執(zhí)行一個(gè)Id = 60的查詢,這個(gè)查詢是添加了排它鎖的方式執(zhí)行的,這個(gè)Id是不存在的
?
在Session2中觀察產(chǎn)生的范圍鎖,這一次發(fā)現(xiàn)resource_description是一個(gè)(ffffffffffff),可以認(rèn)為(ffffffffffff)這個(gè)KeyHashValue是一個(gè)無(wú)窮大的值
那么問(wèn)題就來(lái)了,鎖定范圍的上限是一個(gè)無(wú)窮大的值,那么下限在哪里?
同樣,可以在Session2中采用共享鎖探測(cè)的方式來(lái)觀察Session1鎖定的范圍
測(cè)試1,在Session2中查詢Id = 70的值,Id = 70是大于表中的一個(gè)最大值,被鎖定(鎖定范圍上限為無(wú)窮大,同理更大值也能被鎖定)
測(cè)試1,在Session2中查詢Id = 50的值,Id = 50是表中的一個(gè)最大值,被鎖定
測(cè)試3,在Session2中查詢Id = 49的值,Id = 49是小于表中的一個(gè)最大值,未被鎖定,盡管這個(gè)值不存在
?
測(cè)試4,在Session2中查詢Id = 40的值,Id = 40是小于表中的一個(gè)最大值且存在的值,未被鎖定
?
當(dāng)鎖定的目標(biāo)在表中不存在的時(shí)候,且鎖定目標(biāo)大于表中已存在的最大Id值,那么鎖定的區(qū)間就是從表中最大值開(kāi)始到無(wú)窮大的一個(gè)區(qū)間。
同樣用一個(gè)圖來(lái)表示,看起來(lái)更直觀一點(diǎn)
?
?
4,關(guān)于索引是否唯一與鎖定期間臨界值的關(guān)系
?上文測(cè)試過(guò)程中,給出的Key與其對(duì)應(yīng)的范圍鎖的鎖定關(guān)系中如下,鎖定范圍是包含了臨界值的(雙閉區(qū)間),但是一直沒(méi)有刻意測(cè)試臨界值。
沒(méi)有刻意測(cè)試臨界值是因?yàn)榕R界值是否被鎖定,是跟索引的唯一性有關(guān),如果索引時(shí)非唯一的,對(duì)應(yīng)的范圍鎖在鎖定的時(shí)候就包含臨界值,如果索引唯一,情況是不一樣的。
下文中會(huì)有說(shuō)明。
對(duì)于唯一索引,分為以下幾種情況:
?
? 4.1 唯一索引情況下,鎖定目標(biāo)為已存在的Id值,且Id值大于表中的最小Id,小于表中的最大Id
在索引唯一的情況下,鎖定目標(biāo)是一個(gè)表中已存在的Id值,那么究竟是不是范圍鎖?
很多人認(rèn)為如果鎖定目標(biāo)是已存在的唯一索引,沒(méi)有產(chǎn)生Range鎖的時(shí)候就沒(méi)有“范圍鎖”的概念了,其實(shí)是不對(duì)的。
繼續(xù)測(cè)試,回滾Session1,Session2,刪除表中一開(kāi)始創(chuàng)建的非唯一索引,Id上創(chuàng)建成一個(gè)唯一的聚集索引。
測(cè)試在觀察數(shù)據(jù)的索引頁(yè),發(fā)生了變化(重建了聚集索引,數(shù)據(jù)頁(yè)發(fā)生了變化,想一想為什么?)
用同樣的方式得到數(shù)據(jù)的KeyHashValue與數(shù)據(jù)行的對(duì)應(yīng)關(guān)系如下
10:d08358b1108f
20:286fc18d83ea
30:8034b699f2c9
40:d8b6f3f4a521
50:f84b73ce9e8d
同理在Session1中查詢一個(gè)已存在的Id值,作為鎖定目標(biāo)
在Session2中觀察產(chǎn)生的鎖,鎖定的行是很明顯是Id = 30的數(shù)據(jù)行,但是是一個(gè)X鎖,而非范圍鎖(RangeX-X)。
那么此時(shí),僅僅是會(huì)鎖定當(dāng)前行嗎?
? ? 測(cè)試1,在Session2中查詢一個(gè)小于輸定目標(biāo)(但是大于20,因?yàn)?0是小于鎖定目標(biāo)的已存在的最大值)的值,發(fā)現(xiàn)依舊是被鎖定,
? ?
測(cè)試2,再測(cè)一個(gè)Id =29的值,一樣是被鎖定的
這里捎帶看一下Session2(Sess_id = 55)被Session1(Session_id = 54)的阻塞情況
這里的wait_type為L(zhǎng)CK_M_RS_S,LCK_M_RS_S是啥鎖?LCK_M_RS_S:等待獲取當(dāng)前鍵值上的共享鎖以及當(dāng)前鍵和上一個(gè)鍵之間的共享范圍鎖
依舊是是“當(dāng)前鍵和上一個(gè)鍵之間的共享范圍鎖”啊,依舊是范圍鎖啊,因此說(shuō),鎖定已存在與表中的唯一索引的時(shí)候,雖然沒(méi)有變現(xiàn)出來(lái)范圍鎖(sys.dm_tran_locks),但是本質(zhì)上仍然是范圍鎖。
測(cè)試3,測(cè)試一個(gè)小于鎖定目標(biāo),且存在與表中的最大值(也就是20),發(fā)現(xiàn)未被鎖定(這就是唯一索引與非唯一索引在臨界值上的鎖定區(qū)別,如果是非唯一索引,這個(gè)20的臨界值將會(huì)被鎖定)
測(cè)試4,測(cè)試一個(gè)大于鎖定區(qū)間的值,也即如下的Id = 31,查詢是成功的,即便是Id= 31不存在的。
?
? 從中可以發(fā)現(xiàn),在唯一索引的情況下,
如果鎖定的目標(biāo)Id的值存在與表中,且大于表中的最大值,小于表中的最小值,那么鎖定的區(qū)間就是當(dāng)前值到小于鎖定目標(biāo)的第一個(gè)最大值
具實(shí)際例子來(lái)說(shuō)就是,鎖定目標(biāo)是30的情況下,鎖定的區(qū)間值是(20,30]
?
?
? 4.2 唯一索引情況下,鎖定目標(biāo)為不存在的Id值,且Id值大于表中的最小Id,小于表中的最大Id
這種情況就不一一截圖了,結(jié)論如同非唯一索引,比如鎖定目標(biāo)為Id = 35的情況下,鎖的范圍是(30,40],也即左開(kāi)(區(qū)間)右閉(區(qū)間)
4.3 唯一索引情況下,鎖定目標(biāo)為不存在的Id值,且Id值大于表中的最大Id?
這種情況也就不一一截圖了,結(jié)論如同非唯一索引,比如鎖定目標(biāo)為Id = 60的情況下,鎖的范圍是(50,+∞),也即左開(kāi)(區(qū)間)
?
5,關(guān)于查詢條件是一個(gè)區(qū)間值的情況
因?yàn)橹懒藛蝹€(gè)值查詢的鎖的區(qū)間,對(duì)于范圍查詢的情況,無(wú)非就是將查詢范圍進(jìn)行分解,分解出單個(gè)值鎖定的范圍,然后將這個(gè)區(qū)間進(jìn)行合并得到一個(gè)區(qū)間的并集。
有興趣的可以自行測(cè)試。
6,關(guān)于查詢條件是一個(gè)非聚集索引的情況
上述都是以聚集索引為查詢條件進(jìn)行測(cè)試的,如果是非聚集索引情況雷同,只不過(guò)是多了非聚集索引一級(jí)的鎖,有時(shí)間再測(cè)試。
?
總結(jié):
序列化隔離級(jí)別下會(huì)阻止幻讀的產(chǎn)生,幻讀的產(chǎn)生是通過(guò)范圍鎖鎖定的是一個(gè)范圍來(lái)實(shí)現(xiàn)的,
Range 鎖最主要的是鎖定一個(gè)范圍,鎖定的不僅僅是表中已有的數(shù)據(jù),而是一個(gè)區(qū)間,而不管這個(gè)范圍之內(nèi)是否存在數(shù)據(jù),
任何Session試圖操作被其他Session范圍鎖鎖定的數(shù)據(jù),不管在表中是否存在,都將被阻塞,直到產(chǎn)生范圍鎖的Session事物提交。
此時(shí)也不難理解,對(duì)于那個(gè)最經(jīng)典的問(wèn)題:并發(fā)情況下,存在則更新,不存在則插入,不管采用什么寫(xiě)法,
比如并發(fā)插入,任何一個(gè)Session執(zhí)行之前,都先鎖定一個(gè)范圍,即便是這個(gè)值不存在,
等到相同的值進(jìn)來(lái)的時(shí)候,同樣需要鎖定一個(gè)范圍,那么此時(shí)是會(huì)被阻塞的,因此可以實(shí)現(xiàn)并發(fā)存在則更新,不存在則插入的效果
了解了Range鎖的鎖定原理,也不會(huì)糾結(jié)不同寫(xiě)法的區(qū)別了,目的都是加Range鎖,鎖定范圍,防止并發(fā)情況下的幻讀出現(xiàn)。
以上純屬個(gè)人測(cè)試和簡(jiǎn)單的推斷,難免存在錯(cuò)誤的地方,如有興趣,歡迎探討指正,謝謝。
?
最后
其實(shí)樓主是看了MySQL的gap鎖、next-key鎖之后回頭來(lái)看SQL Server中的Range鎖的,
最終發(fā)現(xiàn),除了一些細(xì)節(jié),鎖的實(shí)現(xiàn)在套路上都是一樣的,比如對(duì)待幻讀的處理上,可謂是在“道”的層面上都是一個(gè)原則。
一個(gè)叫做Range范圍鎖,一個(gè)叫做gap鎖、next-key鎖,不同的表現(xiàn)形式只是“術(shù)”上的問(wèn)題罷了。
?
?
?太累了,眼睛脖子都受不鳥(niǎo)了。
?
參考資料,各種翻書(shū),各種上網(wǎng)查。
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/wy123/p/7501261.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: iframe和HTML5 blob实现J
- 下一篇: mysql系列之2.mysql多实例