日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

SQL Server 鎖機(jī)制 悲觀(guān)鎖 樂(lè)觀(guān)鎖 實(shí)測(cè)解析

 咸咸咸咸魚(yú)干 2019-05-15

先引入一些概念,直接Copy其他Blogs中的,我就不單獨(dú)寫(xiě)了。

一、為什么會(huì)有鎖

多個(gè)用戶(hù)同時(shí)對(duì)數(shù)據(jù)庫(kù)的并發(fā)操作時(shí)會(huì)帶來(lái)以下數(shù)據(jù)不一致的問(wèn)題:

1.丟失更新

A,B兩個(gè)用戶(hù)讀同一數(shù)據(jù)并進(jìn)行修改,其中一個(gè)用戶(hù)的修改結(jié)果破壞了另一個(gè)修改的結(jié)果,比如訂票系統(tǒng)

2.臟讀

A用戶(hù)修改了數(shù)據(jù),隨后B用戶(hù)又讀出該數(shù)據(jù),但A用戶(hù)因?yàn)槟承┰蛉∠藢?duì)數(shù)據(jù)的修改,數(shù)據(jù)恢復(fù)原值,此時(shí)B得到的數(shù)據(jù)就與數(shù)據(jù)庫(kù)內(nèi)的數(shù)據(jù)產(chǎn)生了不一致

3.不可重復(fù)讀

A用戶(hù)讀取數(shù)據(jù),隨后B用戶(hù)讀出該數(shù)據(jù)并修改,此時(shí)A用戶(hù)再讀取數(shù)據(jù)時(shí)發(fā)現(xiàn)前后兩次的值4.不一致

并發(fā)控制的主要方法是封鎖,鎖就是在一段時(shí)間內(nèi)禁止用戶(hù)做某些操作以避免產(chǎn)生數(shù)據(jù)不一致

 

二、鎖的種類(lèi)

共享 (S) 用于不更改或不更新數(shù)據(jù)的操作(只讀操作),如 SELECT 語(yǔ)句。

更新 (U) 用于可更新的資源中。防止當(dāng)多個(gè)會(huì)話(huà)在讀取、鎖定以及隨后可能進(jìn)行的資源更新時(shí)發(fā)生常見(jiàn)形式的死鎖。

排它 (X) 用于數(shù)據(jù)修改操作,例如 INSERT、UPDATE 或 DELETE。確保不會(huì)同時(shí)同一資源進(jìn)行多重更新。

意向鎖 用于建立鎖的層次結(jié)構(gòu)。意向鎖的類(lèi)型為:意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)。

架構(gòu)鎖 在執(zhí)行依賴(lài)于表架構(gòu)的操作時(shí)使用。架構(gòu)鎖的類(lèi)型為:架構(gòu)修改 (Sch-M) 和架構(gòu)穩(wěn)定性 (Sch-S)。

大容量更新 (BU) 向表中大容量復(fù)制數(shù)據(jù)并指定了 TABLOCK 提示時(shí)使用。

共享鎖

共享 (S) 鎖允許并發(fā)事務(wù)讀取 (SELECT) 一個(gè)資源。資源上存在共享 (S) 鎖時(shí),任何其它事務(wù)都不能修改數(shù)據(jù)。一旦已經(jīng)讀取數(shù)據(jù),便立即釋放資源上的共享 (S) 鎖,除非將事務(wù)隔離級(jí)別設(shè)置為可重復(fù)讀或更高級(jí)別,或者在事務(wù)生存周期內(nèi)用鎖定提示保留共享 (S) 鎖。

更新鎖

更新 (U) 鎖可以防止通常形式的死鎖。一般更新模式由一個(gè)事務(wù)組成,此事務(wù)讀取記錄,獲取資源(頁(yè)或行)的共享 (S) 鎖,然后修改行,此操作要求鎖轉(zhuǎn)換為排它 (X) 鎖。如果兩個(gè)事務(wù)獲得了資源上的共享模式鎖,然后試圖同時(shí)更新數(shù)據(jù),則一個(gè)事務(wù)嘗試將鎖轉(zhuǎn)換為排它 (X) 鎖。共享模式到排它鎖的轉(zhuǎn)換必須等待一段時(shí)間,因?yàn)橐粋€(gè)事務(wù)的排它鎖與其它事務(wù)的共享模式鎖不兼容;發(fā)生鎖等待。第二個(gè)事務(wù)試圖獲取排它 (X) 鎖以進(jìn)行更新。由于兩個(gè)事務(wù)都要轉(zhuǎn)換為排它 (X) 鎖,并且每個(gè)事務(wù)都等待另一個(gè)事務(wù)釋放共享模式鎖,因此發(fā)生死鎖。

若要避免這種潛在的死鎖問(wèn)題,請(qǐng)使用更新 (U) 鎖。一次只有一個(gè)事務(wù)可以獲得資源的更新 (U) 鎖。如果事務(wù)修改資源,則更新 (U) 鎖轉(zhuǎn)換為排它 (X) 鎖。否則,鎖轉(zhuǎn)換為共享鎖。

排它鎖

排它 (X) 鎖可以防止并發(fā)事務(wù)對(duì)資源進(jìn)行訪(fǎng)問(wèn)。其它事務(wù)不能讀取或修改排它 (X) 鎖鎖定的數(shù)據(jù)。

意向鎖

意向鎖表示 SQL Server 需要在層次結(jié)構(gòu)中的某些底層資源上獲取共享 (S) 鎖或排它 (X) 鎖。例如,放置在表級(jí)的共享意向鎖表示事務(wù)打算在表中的頁(yè)或行上放置共享 (S) 鎖。在表級(jí)設(shè)置意向鎖可防止另一個(gè)事務(wù)隨后在包含那一頁(yè)的表上獲取排它 (X) 鎖。意向鎖可以提高性能,因?yàn)?SQL Server 僅在表級(jí)檢查意向鎖來(lái)確定事務(wù)是否可以安全地獲取該表上的鎖。而無(wú)須檢查表中的每行或每頁(yè)上的鎖以確定事務(wù)是否可以鎖定整個(gè)表。

意向鎖包括意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)。

 

兩種由程序員定義的鎖

樂(lè)觀(guān)鎖:依靠表中數(shù)據(jù)行內(nèi)的版本戳或時(shí)間戳字段來(lái)人工管理鎖的工作。

悲觀(guān)鎖:使用數(shù)據(jù)庫(kù)或?qū)ο笊咸峁┑逆i機(jī)制來(lái)處理。

死鎖:死鎖的意思就是A用戶(hù)查找表1并獲得了S鎖,B用戶(hù)查找表1也獲得了S鎖,當(dāng)A用戶(hù)找到要更新的行申請(qǐng)X鎖時(shí)被告知B已經(jīng)有S鎖需要等待B解鎖,B用戶(hù)也找到要更新的行申請(qǐng)X鎖時(shí)被告知A已經(jīng)有了S鎖需要等待A解鎖,然后A與B就相互無(wú)休止的等待造成死鎖。

 

三、鎖的粒度也就是范圍

鎖粒度是被封鎖目標(biāo)的大小,封鎖粒度小則并發(fā)性高,但開(kāi)銷(xiāo)大,封鎖粒度大則并發(fā)性低但開(kāi)銷(xiāo)小

SQL Server支持的鎖粒度可以分為為行、頁(yè)、鍵、鍵范圍、索引、表或數(shù)據(jù)庫(kù)獲取鎖

RID      行標(biāo)識(shí)符。用于單獨(dú)鎖定表中的一行。

KEY       索引中的行鎖。用于保護(hù)可串行事務(wù)中的鍵范圍。

PAGE 8    千字節(jié) (KB) 的數(shù)據(jù)頁(yè)或索引頁(yè)。

EXTENT     相鄰的八個(gè)數(shù)據(jù)頁(yè)或索引頁(yè)構(gòu)成的一組。

TABLE     包括所有數(shù)據(jù)和索引在內(nèi)的整個(gè)表。

DATABASE  數(shù)據(jù)庫(kù)。

 

鎖的粒度和鎖的類(lèi)型都是由SQL Server進(jìn)行控制的(當(dāng)然你也可以使用鎖提示,但不推薦)。鎖會(huì)給數(shù)據(jù)庫(kù)帶來(lái)阻塞,因此越大粒度的鎖造成更多的阻塞,但由于大粒度的鎖需要更少的鎖,因此會(huì)提升性能。而小粒度的鎖由于鎖定更少資源,會(huì)減少阻塞,因此提高了并發(fā),但同時(shí)大量的鎖也會(huì)造成性能的下降。 

 

四、鎖的應(yīng)用

在使用SQL時(shí),大都會(huì)遇到這樣的問(wèn)題,你Update一條記錄時(shí),需要通過(guò)Select來(lái)檢索出其值或條件,然后在通過(guò)這個(gè)值來(lái)執(zhí)行修改操作。

但當(dāng)以上操作放到多線(xiàn)程中并發(fā)處理時(shí)會(huì)出現(xiàn)問(wèn)題:某線(xiàn)程select了一條記錄但還沒(méi)來(lái)得及update時(shí),另一個(gè)線(xiàn)程仍然可能會(huì)進(jìn)來(lái)select到同一條記錄。

 一般解決辦法就是使用事物的聯(lián)合機(jī)制:

1. 把select放在事務(wù)中, 否則select完成, 鎖就釋放了
2. 要阻止另一個(gè)select , 則要手工加鎖, select 默認(rèn)是共享鎖, select之間的共享鎖是不沖突的, 所以, 如果只是共享鎖, 即使鎖沒(méi)有釋放, 另一個(gè)select一樣可以下共享鎖, 從而select出數(shù)據(jù) 

BEGIN TRAN
SELECT * FROM Table WITH(UPDLOCK) 
--或者 SELECT * FROM Table WITH(TABLOCKX, READPAST) 具體情況而定。
UPDATE ....
COMMIT TRAN

所有Select加 With (NoLock)解決阻塞死鎖,在查詢(xún)語(yǔ)句中使用 NOLOCK 和 READPAST 
處理一個(gè)數(shù)據(jù)庫(kù)死鎖的異常時(shí)候,其中一個(gè)建議就是使用 NOLOCK 或者 READPAST 。有關(guān) NOLOCK 和 READPAST的一些技術(shù)知識(shí)點(diǎn): 
對(duì)于非銀行等嚴(yán)格要求事務(wù)的行業(yè),搜索記錄中出現(xiàn)或者不出現(xiàn)某條記錄,都是在可容忍范圍內(nèi),所以碰到死鎖,應(yīng)該首先考慮,我們業(yè)務(wù)邏輯是否能容忍出現(xiàn)或者不出現(xiàn)某些記錄,而不是尋求對(duì)雙方都加鎖條件下如何解鎖的問(wèn)題。 
NOLOCK 和 READPAST 都是處理查詢(xún)、插入、刪除等操作時(shí)候,如何應(yīng)對(duì)鎖住的數(shù)據(jù)記錄。但是這時(shí)候一定要注意NOLOCK 和 READPAST的局限性,確認(rèn)你的業(yè)務(wù)邏輯可以容忍這些記錄的出現(xiàn)或者不出現(xiàn): 
簡(jiǎn)單來(lái)說(shuō):

1.NOLOCK 可能把沒(méi)有提交事務(wù)的數(shù)據(jù)也顯示出來(lái)
2.READPAST 會(huì)把被鎖住的行不顯示出來(lái)

不使用 NOLOCK 和 READPAST ,在 Select 操作時(shí)候則有可能報(bào)錯(cuò)誤:事務(wù)(進(jìn)程 ID **)與另一個(gè)進(jìn)程被死鎖在 鎖 資源上,并且已被選作死鎖犧牲品。

SELECT * FROM Table WITH(NOLOCK)
SELECT * FROM Table WITH(READPAST)

鎖描述:

HOLDLOCK:將共享鎖保留到事務(wù)完成,而不是在相應(yīng)的表、行或數(shù)據(jù)頁(yè)不再需要時(shí)就立即釋放鎖。HOLDLOCK等同于 SERIALIZABLE。 
NOLOCK 不要發(fā)出共享鎖,并且不要提供排它鎖。當(dāng)此選項(xiàng)生效時(shí),可能會(huì)讀取未提交的事務(wù)或一組在讀取中間回滾的頁(yè)面。有可能發(fā)生臟讀。僅應(yīng)用于 SELECT 語(yǔ)句。 
PAGLOCK:在通常使用單個(gè)表鎖的地方采用頁(yè)鎖。 
READCOMMITTED:用與運(yùn)行在提交讀隔離級(jí)別的事務(wù)相同的鎖語(yǔ)義執(zhí)行掃描。默認(rèn)情況下,SQL Server 2000在此隔離級(jí)別上操作。 
READPAST:跳過(guò)鎖定行。此選項(xiàng)導(dǎo)致事務(wù)跳過(guò)由其它事務(wù)鎖定的行(這些行平常會(huì)顯示在結(jié)果集內(nèi)),而不是阻塞該事務(wù),使其等待其它事務(wù)釋放在這些行上的鎖。 READPAST 鎖提示僅適用于運(yùn)行在提交讀隔離級(jí)別的事務(wù),并且只在行級(jí)鎖之后讀取。僅適用于 SELECT 語(yǔ)句。 
READUNCOMMITTED:等同于 NOLOCK。 
REPEATABLEREAD:用與運(yùn)行在可重復(fù)讀隔離級(jí)別的事務(wù)相同的鎖語(yǔ)義執(zhí)行掃描。 
ROWLOCK:使用行級(jí)鎖,而不使用粒度更粗的頁(yè)級(jí)鎖和表級(jí)鎖。 
SERIALIZABLE:用與運(yùn)行在可串行讀隔離級(jí)別的事務(wù)相同的鎖語(yǔ)義執(zhí)行掃描。等同于 HOLDLOCK。 
TABLOCK:使用表鎖代替粒度更細(xì)的行級(jí)鎖或頁(yè)級(jí)鎖。在語(yǔ)句結(jié)束前,SQL Server 一直持有該鎖。但是,如果同時(shí)指定 HOLDLOCK,那么在事務(wù)結(jié)束之前,鎖將被一直持有。 
TABLOCKX 使用表的排它鎖。該鎖可以防止其它事務(wù)讀取或更新表,并在語(yǔ)句或事務(wù)結(jié)束前一直持有。 
UPDLOCK:讀取表時(shí)使用更新鎖,而不使用共享鎖,并將鎖一直保留到語(yǔ)句或事務(wù)的結(jié)束。UPDLOCK:的優(yōu)點(diǎn)是允許您讀取數(shù)據(jù)(不阻塞其它事務(wù))并在以后更新數(shù)據(jù),同時(shí)確保自從上次讀取數(shù)據(jù)后數(shù)據(jù)沒(méi)有被更改。 
XLOCK:使用排它鎖并一直保持到由語(yǔ)句處理的所有數(shù)據(jù)上的事務(wù)結(jié)束時(shí)??梢允褂?span lang="EN-US"> PAGLOCK 或 TABLOCK 指定該鎖,這種情況下排它鎖適用于適當(dāng)級(jí)別的粒度。

 

實(shí)際開(kāi)始動(dòng)手用代碼說(shuō)話(huà)吧!

SQLServer2012在查詢(xún)分析器里面開(kāi)兩個(gè)連接

插入鎖:

結(jié)論:“表鎖”鎖定對(duì)該表的Select、Update、Delete操作,但不影響對(duì)該表的Insert操作也不影響以主鍵Id為條件的Select,所以Select如果不想等待就要在Select后加With(Nolock),但這樣會(huì)產(chǎn)生臟數(shù)據(jù)就是其他事務(wù)已更新但并沒(méi)有提交的數(shù)據(jù),如果該事務(wù)進(jìn)行了RollBack則取出的數(shù)據(jù)就是錯(cuò)誤的,所以好自己權(quán)衡利弊,一般情況下90%以上的Select都允許臟讀,只有賬戶(hù)金額相關(guān)的不允許。

復(fù)制代碼
------------------A連接 Insert Lock-------------------
BEGIN TRAN
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'eee', -- Name - varchar(50)
          2, -- Age - int
          '555', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          0  -- Type - int
          )
          
SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode
--ROLLBACK TRAN

------------------------B連接 Insert Lock------------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'fff', -- Name - varchar(50)
          2, -- Age - int
          '123', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          1  -- Type - int
          ) --可以執(zhí)行插入
          
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Age=1 --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執(zhí)行查詢(xún)(根據(jù)主鍵可以)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(xún)(在一個(gè)事務(wù)中,有更新字段但還沒(méi)有提交,此時(shí)就會(huì)查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Age=1 --可以執(zhí)行查詢(xún)
UPDATE dbo.UserInfo SET Type=5 WHERE Name='fff'  --需要等待解鎖
DELETE FROM dbo.UserInfo WHERE Name='fff' --需要等待解鎖
復(fù)制代碼

更新鎖:

結(jié)論:“表鎖”鎖定對(duì)該表的Select、Update、Delete操作,但不影響對(duì)該表的Insert操作也不影響以主鍵Id為條件的Select

復(fù)制代碼
-----------------------A連接 Update Lock-----------------------
BEGIN TRAN
UPDATE dbo.UserInfo SET Name = 'eee' WHERE Age = 2

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--ROLLBACK TRAN

------------------------B連接 Update Lock------------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'ppp', -- Name - varchar(50)
          15, -- Age - int
          '666', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          9  -- Type - int
          ) --可以執(zhí)行插入
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Name='ppp' --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執(zhí)行查詢(xún)(根據(jù)主鍵可以)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(xún)(在一個(gè)事務(wù)中,有更新字段但還沒(méi)有提交,此時(shí)就會(huì)查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'ppp' --可以執(zhí)行查詢(xún)
UPDATE dbo.UserInfo SET Age=8 WHERE Name='ccc' --需要等待解鎖
DELETE dbo.UserInfo WHERE Age = 5 --需要等待解鎖
復(fù)制代碼

主鍵鎖:

結(jié)論:“行鎖+表鎖” 鎖定對(duì)該表的Select、Update、Delete操作,但不影響對(duì)該表的Insert操作也不影響以主鍵Id為條件的Select、Update、Delete

復(fù)制代碼
------------------------A連接 Key Lock--------------------
BEGIN TRAN
UPDATE dbo.UserInfo SET Name='hhh' WHERE Id=3 --以主鍵為條件

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--ROLLBACK TRAN

------------------------B連接 Key Lock----------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'kkk', -- Name - varchar(50)
          18, -- Age - int
          '234', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          7  -- Type - int
          ) --可以執(zhí)行插入
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(xún)(在一個(gè)事務(wù)中,有更新字段但還沒(méi)有提交,此時(shí)就會(huì)查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執(zhí)行查詢(xún)

-----//全表查詢(xún)及操作正在處理的行
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
DELETE dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
-----//使用非主鍵為條件的操作
SELECT * FROM dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
DELETE dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
-----//使用主鍵為條件的操作
SELECT * FROM dbo.UserInfo WHERE id=1 --可以執(zhí)行查詢(xún)(根據(jù)主鍵可以)
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
DELETE dbo.UserInfo WHERE Id=1 --可以執(zhí)行刪除(根據(jù)主鍵可以)
復(fù)制代碼

索引鎖:

結(jié)論:“行鎖+表鎖” 鎖定對(duì)該表的Select、Update、Delete操作,但不影響對(duì)該表的Insert操作也不影響以主鍵Id為條件的Select、Update、Delete,也不影響以索引列Name為條件的Update、Delete但不可以Select

復(fù)制代碼
------------------------A連接 Index Lock--------------------
DROP INDEX dbo.UserInfo.Index_UserInfo_Name
CREATE INDEX Index_UserInfo_Name ON dbo.UserInfo(Name)

BEGIN TRAN
UPDATE dbo.UserInfo SET age=66 WHERE Name='ddd' --使用name索引列為條件

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--ROLLBACK TRAN

----------------------B連接 Index Lock-------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'iii', -- Name - varchar(50)
          20, -- Age - int
          '235235235', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          12  -- Type - int
          ) --可以執(zhí)行插入
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(xún)(在一個(gè)事物中,有更新字段但還沒(méi)有提交,此時(shí)就會(huì)查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執(zhí)行查詢(xún)

-----//全表查詢(xún)及操作正在處理的行
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
DELETE dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
-----//使用非主鍵非索引為條件的操作
SELECT * FROM dbo.UserInfo WHERE Age=5 --需要等待解鎖(非主鍵不可)
UPDATE dbo.UserInfo SET Name='ooo' WHERE Age=5 --需要等待解鎖(非主鍵不可)
DELETE dbo.UserInfo WHERE Age=5 --需要等待解鎖(非主鍵不可)
-----//使用主鍵為條件的操作
SELECT * FROM dbo.UserInfo WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
DELETE dbo.UserInfo WHERE Id=1 --可以執(zhí)行刪除(根據(jù)主鍵可以)
-----//使用索引為條件的操作
SELECT * FROM dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --可以執(zhí)行更新(根據(jù)索引可以)
DELETE dbo.UserInfo WHERE Name='aaa' --可以執(zhí)行刪除(根據(jù)索引可以)
復(fù)制代碼

悲觀(guān)鎖(更新鎖-人工手動(dòng)設(shè)置上鎖):

結(jié)論:可以理解為在使用版本控制軟件的時(shí)候A遷出了一個(gè)文件,并且將這個(gè)文件鎖定,B就無(wú)法再遷出該文件了,直到A遷入解鎖后才能被其他人遷出。

復(fù)制代碼
------------------------A連接 Update Lock(悲觀(guān)鎖)---------------------
BEGIN TRAN
SELECT * FROM dbo.UserInfo WITH(UPDLOCK) WHERE Id=2

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--COMMIT TRAN
--ROLLBACK TRAN

---------------------------B連接 Update Lock(悲觀(guān)鎖)-------------------------
SELECT * FROM dbo.UserInfo --可以執(zhí)行查詢(xún)
SELECT * FROM dbo.UserInfo WHERE id=2 --可以執(zhí)行查詢(xún)
SELECT * FROM dbo.UserInfo WHERE Name='ooo' --可以執(zhí)行查詢(xún)

UPDATE dbo.UserInfo SET Age=3 WHERE id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
UPDATE dbo.UserInfo SET Age=3 WHERE Name='ccc' --需要等待解鎖(非主鍵不可)

DELETE dbo.UserInfo WHERE id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
DELETE dbo.UserInfo WHERE name='ccc' --需要等待解鎖(非主鍵不可)
復(fù)制代碼

樂(lè)觀(guān)鎖(人工通過(guò)邏輯在數(shù)據(jù)庫(kù)中模擬鎖)

結(jié)論:可以理解為同樣在使用版本控制軟件的時(shí)候A遷出了一個(gè)文件,B也可以遷出該文件,兩個(gè)人都可以對(duì)此文件進(jìn)行修改,其中一個(gè)人先進(jìn)行提交的時(shí)候,版本并沒(méi)有變化所以可以正常提交,另一個(gè)后提交的時(shí)候,發(fā)現(xiàn)版本增加不對(duì)稱(chēng)了,就提示沖突由用戶(hù)來(lái)選擇如何進(jìn)行合并再重新進(jìn)行提交。

復(fù)制代碼
--------------------------A客戶(hù)端連接 Lock(樂(lè)觀(guān)鎖)------------------------
--DROP TABLE Coupon
-----------------創(chuàng)建優(yōu)惠券表-----------------
CREATE TABLE Coupon
(
    Id INT PRIMARY KEY IDENTITY(1,1),
    Number VARCHAR(50) NOT NULL,
    [User] VARCHAR(50),
    UseTime DATETIME,
    IsFlag BIT DEFAULT(0) NOT NULL,
    CreateTime DATETIME DEFAULT(GETDATE()) NOT NULL
)
INSERT INTO dbo.Coupon(Number) VALUES ( '10000001')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000002')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000003')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000004')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000005')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000006')

--SELECT * FROM dbo.Coupon WITH(NOLOCK) --查詢(xún)數(shù)據(jù)
--UPDATE Coupon SET [User]=NULL, UseTime=NULL, IsFlag=0 --還原數(shù)據(jù)

-----------------1、模擬高并發(fā)普通更新-----------------
DECLARE @User VARCHAR(50)    --模擬要使用優(yōu)惠券的用戶(hù)
DECLARE @TempId INT            --模擬抽選出來(lái)的要使用的優(yōu)惠券
SET @User='a'
BEGIN TRAN
SELECT @TempId=Id FROM dbo.Coupon WHERE IsFlag=0    --高并發(fā)時(shí)此語(yǔ)句有可能另外一個(gè)該事務(wù)已取出的Id
--WAITFOR DELAY '00:00:05'    --改用此方式要開(kāi)兩個(gè)SQL Management客戶(hù)端
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
COMMIT TRAN
--ROLLBACK TRAN

-----------------2、悲觀(guān)鎖解決方案-----------------
DECLARE @User VARCHAR(50)    --模擬要使用優(yōu)惠券的用戶(hù)
DECLARE @TempId INT            --模擬抽選出來(lái)的要使用的優(yōu)惠券
SET @User='a'
BEGIN TRAN
SELECT @TempId=Id FROM dbo.Coupon WITH(UPDLOCK) WHERE IsFlag=0    --高并發(fā)時(shí)此語(yǔ)句會(huì)鎖定取出的Id數(shù)據(jù)行
--WAITFOR DELAY '00:00:05'    --改用此方式要開(kāi)兩個(gè)SQL Management客戶(hù)端
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
COMMIT TRAN
--ROLLBACK TRAN

-----------------3、樂(lè)觀(guān)鎖解決方案-----------------
ALTER TABLE dbo.Coupon ADD RowVer ROWVERSION NOT NULL --增加數(shù)據(jù)行版本戳類(lèi)型字段(微軟新推薦數(shù)據(jù)字段,該字段每張表只能有一個(gè),會(huì)在創(chuàng)建行或更新行時(shí)自動(dòng)進(jìn)行修改無(wú)需人為干涉,該字段不能建立索引及主鍵因?yàn)闀?huì)頻繁修改)

DECLARE @User VARCHAR(50)    --模擬要使用優(yōu)惠券的用戶(hù)
DECLARE @TempId INT            --模擬抽選出來(lái)的要使用的優(yōu)惠券
DECLARE @RowVer BINARY(8)    --抽選出來(lái)的優(yōu)惠券的版本(ROWVERSION數(shù)據(jù)類(lèi)型存儲(chǔ)大小為8字節(jié))
SET @User='a'

BEGIN TRY
    BEGIN TRAN
    SELECT @TempId=Id, @RowVer=RowVer FROM dbo.Coupon WHERE IsFlag=0    --取出可用的Id及對(duì)應(yīng)的版本戳
    --WAITFOR DELAY '00:00:05'    --改用此方式要開(kāi)兩個(gè)SQL Management客戶(hù)端
    UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId AND RowVer=@RowVer
    IF(@@ROWCOUNT > 0)
        BEGIN
            PRINT('修改成功')
            COMMIT TRAN
        END
    ELSE
        BEGIN
            PRINT('該數(shù)據(jù)已被其他用戶(hù)修改')
            ROLLBACK TRAN
        END
END TRY
BEGIN CATCH
    ROLLBACK TRAN
END CATCH

--------------------------B客戶(hù)端連接 Lock(樂(lè)觀(guān)鎖)------------------------
--此測(cè)試需要開(kāi)兩個(gè)SQL Management Studio客戶(hù)端,在A客戶(hù)端使用WAITFOR DELAY來(lái)模擬并發(fā)占用,在B客戶(hù)端執(zhí)行與A客戶(hù)端相同的SQL腳本即可(注釋掉WAITFOR),所以在此不放相同代碼了。
復(fù)制代碼

在樂(lè)觀(guān)鎖和悲觀(guān)鎖之間進(jìn)行選擇的標(biāo)準(zhǔn)是:沖突的頻率與嚴(yán)重性。如果沖突很少,或者沖突的后果不會(huì)很?chē)?yán)重,那么通常情況下應(yīng)該選擇樂(lè)觀(guān)鎖,因?yàn)樗艿玫礁玫牟l(fā)性,而且更容易實(shí)現(xiàn)。但是,如果沖突的結(jié)果對(duì)于用戶(hù)來(lái)說(shuō)痛苦的,那么就需要使用悲觀(guān)策略。

我認(rèn)為如果同一張表的并發(fā)很高,但并發(fā)處理同一條數(shù)據(jù)的沖突幾率很低,那就應(yīng)該使用樂(lè)觀(guān)鎖,反之,如果同一張表的并發(fā)不高,但同時(shí)處理同一條數(shù)據(jù)的幾率很高,就應(yīng)該使用悲觀(guān)鎖。

 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀(guān)點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多