SQL Server 鎖機制 悲觀鎖 樂觀鎖 實測解析

 

一、爲什麼會有鎖

多個用戶同時對數據庫的併發操作時會帶來以下數據不一致的問題:

1.丟失更新

A,B兩個用戶讀同一數據並進行修改,其中一個用戶的修改結果破壞了另一個修改的結果,比如訂票系統

2.髒讀

A用戶修改了數據,隨後B用戶又讀出該數據,但A用戶因爲某些原因取消了對數據的修改,數據恢復原值,此時B得到的數據就與數據庫內的數據產生了不一致

3.不可重複讀

A用戶讀取數據,隨後B用戶讀出該數據並修改,此時A用戶再讀取數據時發現前後兩次的值4.不一致

併發控制的主要方法是封鎖,鎖就是在一段時間內禁止用戶做某些操作以避免產生數據不一致

 

二、鎖的種類

共享 (S) 用於不更改或不更新數據的操作(只讀操作),如 SELECT 語句。

更新 (U) 用於可更新的資源中。防止當多個會話在讀取、鎖定以及隨後可能進行的資源更新時發生常見形式的死鎖。

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

意向鎖 用於建立鎖的層次結構。意向鎖的類型爲:意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)。

架構鎖 在執行依賴於表架構的操作時使用。架構鎖的類型爲:架構修改 (Sch-M) 和架構穩定性 (Sch-S)。

大容量更新 (BU) 向表中大容量複製數據並指定了 TABLOCK 提示時使用。

共享鎖

共享 (S) 鎖允許併發事務讀取 (SELECT) 一個資源。資源上存在共享 (S) 鎖時,任何其它事務都不能修改數據。一旦已經讀取數據,便立即釋放資源上的共享 (S) 鎖,除非將事務隔離級別設置爲可重複讀或更高級別,或者在事務生存週期內用鎖定提示保留共享 (S) 鎖。

更新鎖

更新 (U) 鎖可以防止通常形式的死鎖。一般更新模式由一個事務組成,此事務讀取記錄,獲取資源(頁或行)的共享 (S) 鎖,然後修改行,此操作要求鎖轉換爲排它 (X) 鎖。如果兩個事務獲得了資源上的共享模式鎖,然後試圖同時更新數據,則一個事務嘗試將鎖轉換爲排它 (X) 鎖。共享模式到排它鎖的轉換必須等待一段時間,因爲一個事務的排它鎖與其它事務的共享模式鎖不兼容;發生鎖等待。第二個事務試圖獲取排它 (X) 鎖以進行更新。由於兩個事務都要轉換爲排它 (X) 鎖,並且每個事務都等待另一個事務釋放共享模式鎖,因此發生死鎖。

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

排它鎖

排它 (X) 鎖可以防止併發事務對資源進行訪問。其它事務不能讀取或修改排它 (X) 鎖鎖定的數據。

意向鎖

意向鎖表示 SQL Server 需要在層次結構中的某些底層資源上獲取共享 (S) 鎖或排它 (X) 鎖。例如,放置在表級的共享意向鎖表示事務打算在表中的頁或行上放置共享 (S) 鎖。在表級設置意向鎖可防止另一個事務隨後在包含那一頁的表上獲取排它 (X) 鎖。意向鎖可以提高性能,因爲 SQL Server 僅在表級檢查意向鎖來確定事務是否可以安全地獲取該表上的鎖。而無須檢查表中的每行或每頁上的鎖以確定事務是否可以鎖定整個表。

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

 

兩種由程序員定義的鎖

樂觀鎖:依靠表中數據行內的版本戳或時間戳字段來人工管理鎖的工作。

悲觀鎖:使用數據庫或對象上提供的鎖機制來處理。

死鎖:死鎖的意思就是A用戶查找表1並獲得了S鎖,B用戶查找表1也獲得了S鎖,當A用戶找到要更新的行申請X鎖時被告知B已經有S鎖需要等待B解鎖,B用戶也找到要更新的行申請X鎖時被告知A已經有了S鎖需要等待A解鎖,然後A與B就相互無休止的等待造成死鎖。

 

三、鎖的粒度也就是範圍

鎖粒度是被封鎖目標的大小,封鎖粒度小則併發性高,但開銷大,封鎖粒度大則併發性低但開銷小

SQL Server支持的鎖粒度可以分爲爲行、頁、鍵、鍵範圍、索引、表或數據庫獲取鎖

RID      行標識符。用於單獨鎖定表中的一行。

KEY       索引中的行鎖。用於保護可串行事務中的鍵範圍。

PAGE 8    千字節 (KB) 的數據頁或索引頁。

EXTENT     相鄰的八個數據頁或索引頁構成的一組。

TABLE     包括所有數據和索引在內的整個表。

DATABASE  數據庫。

 

鎖的粒度和鎖的類型都是由SQL Server進行控制的(當然你也可以使用鎖提示,但不推薦)。鎖會給數據庫帶來阻塞,因此越大粒度的鎖造成更多的阻塞,但由於大粒度的鎖需要更少的鎖,因此會提升性能。而小粒度的鎖由於鎖定更少資源,會減少阻塞,因此提高了併發,但同時大量的鎖也會造成性能的下降。 

 

四、鎖的應用

在使用SQL時,大都會遇到這樣的問題,你Update一條記錄時,需要通過Select來檢索出其值或條件,然後在通過這個值來執行修改操作。

但當以上操作放到多線程中併發處理時會出現問題:某線程select了一條記錄但還沒來得及update時,另一個線程仍然可能會進來select到同一條記錄。

 一般解決辦法就是使用事物的聯合機制:

1. 把select放在事務中, 否則select完成, 鎖就釋放了
2. 要阻止另一個select , 則要手工加鎖, select 默認是共享鎖, select之間的共享鎖是不衝突的, 所以, 如果只是共享鎖, 即使鎖沒有釋放, 另一個select一樣可以下共享鎖, 從而select出數據 

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

所有Select加 With (NoLock)解決阻塞死鎖,在查詢語句中使用 NOLOCK 和 READPAST 
處理一個數據庫死鎖的異常時候,其中一個建議就是使用 NOLOCK 或者 READPAST 。有關 NOLOCK 和 READPAST的一些技術知識點: 
對於非銀行等嚴格要求事務的行業,搜索記錄中出現或者不出現某條記錄,都是在可容忍範圍內,所以碰到死鎖,應該首先考慮,我們業務邏輯是否能容忍出現或者不出現某些記錄,而不是尋求對雙方都加鎖條件下如何解鎖的問題。 
NOLOCK 和 READPAST 都是處理查詢、插入、刪除等操作時候,如何應對鎖住的數據記錄。但是這時候一定要注意NOLOCK 和 READPAST的侷限性,確認你的業務邏輯可以容忍這些記錄的出現或者不出現: 
簡單來說:

1.NOLOCK 可能把沒有提交事務的數據也顯示出來
2.READPAST 會把被鎖住的行不顯示出來

不使用 NOLOCK 和 READPAST ,在 Select 操作時候則有可能報錯誤:事務(進程 ID **)與另一個進程被死鎖在 鎖 資源上,並且已被選作死鎖犧牲品。

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

鎖描述:

HOLDLOCK:將共享鎖保留到事務完成,而不是在相應的表、行或數據頁不再需要時就立即釋放鎖。HOLDLOCK等同於 SERIALIZABLE。 
NOLOCK 不要發出共享鎖,並且不要提供排它鎖。當此選項生效時,可能會讀取未提交的事務或一組在讀取中間回滾的頁面。有可能發生髒讀。僅應用於 SELECT 語句。 
PAGLOCK:在通常使用單個表鎖的地方採用頁鎖。 
READCOMMITTED:用與運行在提交讀隔離級別的事務相同的鎖語義執行掃描。默認情況下,SQL Server 2000在此隔離級別上操作。 
READPAST:跳過鎖定行。此選項導致事務跳過由其它事務鎖定的行(這些行平常會顯示在結果集內),而不是阻塞該事務,使其等待其它事務釋放在這些行上的鎖。 READPAST 鎖提示僅適用於運行在提交讀隔離級別的事務,並且只在行級鎖之後讀取。僅適用於 SELECT 語句。 
READUNCOMMITTED:等同於 NOLOCK。 
REPEATABLEREAD:用與運行在可重複讀隔離級別的事務相同的鎖語義執行掃描。 
ROWLOCK:使用行級鎖,而不使用粒度更粗的頁級鎖和表級鎖。 
SERIALIZABLE:用與運行在可串行讀隔離級別的事務相同的鎖語義執行掃描。等同於 HOLDLOCK。 
TABLOCK:使用表鎖代替粒度更細的行級鎖或頁級鎖。在語句結束前,SQL Server 一直持有該鎖。但是,如果同時指定 HOLDLOCK,那麼在事務結束之前,鎖將被一直持有。 
TABLOCKX 使用表的排它鎖。該鎖可以防止其它事務讀取或更新表,並在語句或事務結束前一直持有。 
UPDLOCK:讀取表時使用更新鎖,而不使用共享鎖,並將鎖一直保留到語句或事務的結束。UPDLOCK:的優點是允許您讀取數據(不阻塞其它事務)並在以後更新數據,同時確保自從上次讀取數據後數據沒有被更改。 
XLOCK:使用排它鎖並一直保持到由語句處理的所有數據上的事務結束時。可以使用 PAGLOCK 或 TABLOCK 指定該鎖,這種情況下排它鎖適用於適當級別的粒度。

 

實際開始動手用代碼說話吧!

SQLServer2012在查詢分析器裏面開兩個連接

插入鎖:

結論:“表鎖”鎖定對該表的Select、Update、Delete操作,但不影響對該表的Insert操作也不影響以主鍵Id爲條件的Select,所以Select如果不想等待就要在Select後加With(Nolock),但這樣會產生髒數據就是其他事務已更新但並沒有提交的數據,如果該事務進行了RollBack則取出的數據就是錯誤的,所以好自己權衡利弊,一般情況下90%以上的Select都允許髒讀,只有賬戶金額相關的不允許。

複製代碼
------------------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
          ) --可以執行插入
          
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Age=1 --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執行查詢(根據主鍵可以)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執行查詢(在一個事務中,有更新字段但還沒有提交,此時就會查處髒數據)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Age=1 --可以執行查詢
UPDATE dbo.UserInfo SET Type=5 WHERE Name='fff'  --需要等待解鎖
DELETE FROM dbo.UserInfo WHERE Name='fff' --需要等待解鎖
複製代碼

更新鎖:

結論:“表鎖”鎖定對該表的Select、Update、Delete操作,但不影響對該表的Insert操作也不影響以主鍵Id爲條件的Select

複製代碼
-----------------------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
          ) --可以執行插入
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Name='ppp' --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執行查詢(根據主鍵可以)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執行查詢(在一個事務中,有更新字段但還沒有提交,此時就會查處髒數據)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'ppp' --可以執行查詢
UPDATE dbo.UserInfo SET Age=8 WHERE Name='ccc' --需要等待解鎖
DELETE dbo.UserInfo WHERE Age = 5 --需要等待解鎖
複製代碼

主鍵鎖:

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

複製代碼
------------------------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
          ) --可以執行插入
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執行查詢(在一個事務中,有更新字段但還沒有提交,此時就會查處髒數據)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執行查詢

-----//全表查詢及操作正在處理的行
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據主鍵,但與A連接操作相同行不可)
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=3 --需要等待解鎖(根據主鍵,但與A連接操作相同行不可)
DELETE dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據主鍵,但與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 --可以執行查詢(根據主鍵可以)
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執行更新(根據主鍵可以)
DELETE dbo.UserInfo WHERE Id=1 --可以執行刪除(根據主鍵可以)
複製代碼

索引鎖:

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

複製代碼
------------------------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
          ) --可以執行插入
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執行查詢(在一個事物中,有更新字段但還沒有提交,此時就會查處髒數據)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執行查詢

-----//全表查詢及操作正在處理的行
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據主鍵,但與A連接操作相同行不可)
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=4 --需要等待解鎖(根據主鍵,但與A連接操作相同行不可)
DELETE dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據主鍵,但與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 --可以執行更新(根據主鍵可以)
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執行更新(根據主鍵可以)
DELETE dbo.UserInfo WHERE Id=1 --可以執行刪除(根據主鍵可以)
-----//使用索引爲條件的操作
SELECT * FROM dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --可以執行更新(根據索引可以)
DELETE dbo.UserInfo WHERE Name='aaa' --可以執行刪除(根據索引可以)
複製代碼

悲觀鎖(更新鎖-人工手動設置上鎖):

結論:可以理解爲在使用版本控制軟件的時候A遷出了一個文件,並且將這個文件鎖定,B就無法再遷出該文件了,直到A遷入解鎖後才能被其他人遷出。

複製代碼
------------------------A連接 Update Lock(悲觀鎖)---------------------
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(悲觀鎖)-------------------------
SELECT * FROM dbo.UserInfo --可以執行查詢
SELECT * FROM dbo.UserInfo WHERE id=2 --可以執行查詢
SELECT * FROM dbo.UserInfo WHERE Name='ooo' --可以執行查詢

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

DELETE dbo.UserInfo WHERE id=1 --可以執行更新(根據主鍵可以)
DELETE dbo.UserInfo WHERE name='ccc' --需要等待解鎖(非主鍵不可)
複製代碼

樂觀鎖(人工通過邏輯在數據庫中模擬鎖)

結論:可以理解爲同樣在使用版本控制軟件的時候A遷出了一個文件,B也可以遷出該文件,兩個人都可以對此文件進行修改,其中一個人先進行提交的時候,版本並沒有變化所以可以正常提交,另一個後提交的時候,發現版本增加不對稱了,就提示衝突由用戶來選擇如何進行合併再重新進行提交。

複製代碼
--------------------------A客戶端連接 Lock(樂觀鎖)------------------------
--DROP TABLE Coupon
-----------------創建優惠券表-----------------
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) --查詢數據
--UPDATE Coupon SET [User]=NULL, UseTime=NULL, IsFlag=0 --還原數據

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

-----------------2、悲觀鎖解決方案-----------------
DECLARE @User VARCHAR(50)    --模擬要使用優惠券的用戶
DECLARE @TempId INT            --模擬抽選出來的要使用的優惠券
SET @User='a'
BEGIN TRAN
SELECT @TempId=Id FROM dbo.Coupon WITH(UPDLOCK) WHERE IsFlag=0    --高併發時此語句會鎖定取出的Id數據行
--WAITFOR DELAY '00:00:05'    --改用此方式要開兩個SQL Management客戶端
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
COMMIT TRAN
--ROLLBACK TRAN

-----------------3、樂觀鎖解決方案-----------------
ALTER TABLE dbo.Coupon ADD RowVer ROWVERSION NOT NULL --增加數據行版本戳類型字段(微軟新推薦數據字段,該字段每張表只能有一個,會在創建行或更新行時自動進行修改無需人爲干涉,該字段不能建立索引及主鍵因爲會頻繁修改)

DECLARE @User VARCHAR(50)    --模擬要使用優惠券的用戶
DECLARE @TempId INT            --模擬抽選出來的要使用的優惠券
DECLARE @RowVer BINARY(8)    --抽選出來的優惠券的版本(ROWVERSION數據類型存儲大小爲8字節)
SET @User='a'

BEGIN TRY
    BEGIN TRAN
    SELECT @TempId=Id, @RowVer=RowVer FROM dbo.Coupon WHERE IsFlag=0    --取出可用的Id及對應的版本戳
    --WAITFOR DELAY '00:00:05'    --改用此方式要開兩個SQL Management客戶端
    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('該數據已被其他用戶修改')
            ROLLBACK TRAN
        END
END TRY
BEGIN CATCH
    ROLLBACK TRAN
END CATCH

--------------------------B客戶端連接 Lock(樂觀鎖)------------------------
--此測試需要開兩個SQL Management Studio客戶端,在A客戶端使用WAITFOR DELAY來模擬併發佔用,在B客戶端執行與A客戶端相同的SQL腳本即可(註釋掉WAITFOR),所以在此不放相同代碼了。
複製代碼

在樂觀鎖和悲觀鎖之間進行選擇的標準是:衝突的頻率與嚴重性。如果衝突很少,或者衝突的後果不會很嚴重,那麼通常情況下應該選擇樂觀鎖,因爲它能得到更好的併發性,而且更容易實現。但是,如果衝突的結果對於用戶來說痛苦的,那麼就需要使用悲觀策略。

我認爲如果同一張表的併發很高,但併發處理同一條數據的衝突機率很低,那就應該使用樂觀鎖,反之,如果同一張表的併發不高,但同時處理同一條數據的機率很高,就應該使用悲觀鎖。

  轉載自:https://www.cnblogs.com/taiyonghai/p/5674462.html

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章