天天看點

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  本文介紹了處理.NET中鎖的6種方法,首先我們讨論一下并發性問題,然後讨論處理樂觀鎖的3種方法,樂觀鎖不能從根源上解決并發問題,是以後面我們介紹了悲觀鎖,最後介紹隔離級别如何幫助我們實作悲觀鎖,每個隔離級别都列舉了示例進行說明,使得概念更加清晰。

  我們為什麼需要鎖?

  在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這就會産生沖突,這個就是著名的并發性問題。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  圖 1 并行性問題漫畫

  如何解決并發性問題?

  借助正确的鎖定政策可以解決并發性問題,資源被鎖定後,其它程序想要通路它就會被阻止。

  并發會造成什麼樣的沖突?

  并發主要會導緻四種常見的問題,詳細情況請看下表。

問題

簡要描述

解釋

髒讀取

當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取

使用者A和使用者B看到的值都是5

 使用者B将值修改為2

 使用者A看到的值仍然是5,這時就發生了髒讀取

不可重複讀取

在每次讀資料時,如果你獲得的值都不一樣,那表明你遇到了不可重複讀取問題

使用者A看到的值是5

使用者B将值改為2

使用者A重新整理後看到的值仍然是5,這時就發生了不可重複讀取

虛幻行

如果update和delete SQL語句未對資料造成影響,很可能遇到了虛幻行問題

使用者A将所有值從5修改為2

使用者B使用值2插入一個新記錄

使用者A查詢所有值為2的記錄,但卻找不到,這時就發生了虛幻行

更新丢失

一個事務的更新覆寫了其它事務的更新結果,就是所謂的更新丢失

使用者A将所有值從5更新為2

使用者B将所有值從2更新到5

 使用者A丢失了他的更新

  如何解決上述沖突?

  答案是使用樂觀鎖或悲觀鎖,下面将進一步進行闡述。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  圖 2 樂觀鎖和悲觀鎖  

  什麼是樂觀鎖?

  顧名思義,樂觀鎖假設多個事務互相不會影響對方,換句話說就是,在樂觀鎖模式下,沒有鎖操作會得到執行,事務隻是驗證是否有其它事務修改資料,如果有則進行事務復原,否則就送出。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

 圖 3 樂觀鎖

  樂觀鎖是如何工作的?

  實作樂觀鎖的方法有多種,但基本原則都一樣,總是少不了下面五個步驟:

  •記錄目前的時間戳

  •開始修改值

  •在更新前,檢查是否有其他人更新了值(通過檢查新舊時間戳實作)

  • 如果不相等就復原,否則就送出

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  圖 4 樂觀鎖的工作原理

  實作樂觀鎖的解決方案

  在.NET中,實作樂觀鎖的方法主要有三種:

  •資料集(Dataset):資料集是實作樂觀鎖的預設方法,在更新前它會檢查新舊值。

  • 時間戳資料類型(timestamp):在你的表中建立一個timestamp資料類型,在更新時,檢查舊時間戳是否等于新時間戳。

  •直接檢查新舊值:在更新時檢查舊值和新值是否相等,如果不相等就復原,否則就送出。

  解決方案1:資料集

  正如前面所說的,資料集是處理樂觀鎖的預設方法,下面是一個簡單的快照,在Adapter的update函數上有一個調試點,當我移除斷點運作update函數時,它抛出如下圖所示的并行異常錯誤。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  圖 5 Update函數執行時抛出的異常錯誤

  如果你運作後端分析器,你将會看到更新語句檢查目前值和舊值是否相等: 

exec sp_executesql N'UPDATE [tbl_items] SET [AuthorName] = @p1

 WHERE (([Id] = @p2) AND ((@p3 = 1 AND [ItemName] IS NULL)

OR ([ItemName] = @p4)) AND ((@p5 = 1 AND [Type] IS NULL)

OR ([Type] = @p6)) AND ((@p7 = 1 AND [AuthorName] IS NULL)

OR ([AuthorName] = @p8)) AND ((@p9 = 1 AND [Vendor] IS NULL)

OR ([Vendor] = @p10)))',N'@p1 nvarchar(11),@p2 int,@p3

int,@p4 nvarchar(4),@p5 int,@p6 int,@p7

int,@p8 nvarchar(18),@p9 int,@p10 nvarchar(2)',

@p1=N'this is new',@p2=2,@p3=0,@p4=N'1001',@p5=0,@p6=3,@p7=0,

@p8=N'This is Old

Author',@p9=0,@p10=N'kk'

  在這種情況下,我嘗試将“AuthorName”字段值修改為“This is new”,但更新時會檢查舊值“This is old author”,下面是比較舊值的精簡代碼段: 

,@p8=N'This is Old Author'

  解決方案2:使用timestamp資料類型

  SQL Server有一個資料類型是timestamp,它是實作樂觀鎖的另一種途徑,每次更新SQL Server資料時,時間戳會自動産生一個唯一的二進制數值,時間戳資料類型可用來版本化你的記錄更新。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

 圖 6 timestamp資料類型

  為了實作樂觀鎖,首先需要取得舊的時間戳值,在更新時檢查舊的時間戳值是否等于目前時間戳,如:

update tbl_items set itemname=@itemname where CurrentTimestamp=@OldTimeStamp

  然後檢查是否發生了更新操作,如果沒有發生更新,則使用SQL Server的raiserror産生一系列錯誤消息。 

if(@@rowcount=0)

begin

raiserror('Hello some else changed the value',16,10)

end

  如果發生了并發沖突,當你如下圖所示這樣調用ExecuteNonQuery時,你應該會看到錯誤傳播。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  圖 7 時間戳發生變化,存儲過程産生了錯誤

  解決方案3:檢查舊值和新值

  許多時候,我們隻需要檢查相關字段值的一緻性,其它字段則可以忽略,在update語句中,我們可以直接做這種比較。

update tbl_items set itemname=@itemname where itemname=@OldItemNameValue

  但使用樂觀鎖似乎沒有完全解決問題,使用樂觀鎖隻能檢查并發性問題,為了從根源上解決并發性問題,我們需要使用悲觀鎖,是以樂觀鎖能起到預防作用,而悲觀鎖則能治愈。

  什麼是悲觀鎖?

  悲觀鎖總是假定會發生并發性/沖突問題,是以會先對記錄上鎖,然後再更新。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

圖 8 悲觀鎖

  如何處理悲觀鎖?

  我們可以在SQL Server存儲過程中指定IsolationLevel(隔離級别),ADO.NET級别或使用事務範圍對象處理悲觀鎖。

  使用悲觀鎖可以獲得什麼樣的鎖?

  使用悲觀鎖可以獲得四種類型的鎖:共享(Shared)、獨占(Exclusive),更新(Update)和意圖(Intent),前兩個是真實的鎖,後面兩個是鎖和标記的混合。

何時使用

允許讀

允許寫

共享鎖

當你隻想讀,不希望其它事務進行更新時

獨占鎖

當你想修改資料,同時不希望别人可以讀,直到你更新完畢時

更新鎖

這是一個混合鎖,當你執行的更新操作有多個步驟時使用

讀階段

操作階段

更新階段

意向鎖(請求鎖)

意向鎖是分級的,當你想鎖定下級資源時使用,例如,在表上的一個共享意向鎖意味着共享鎖是針對頁面和表中的行的,

不适用

模式鎖

當你修改表結構時使用

大資料塊更新鎖

當你執行大資料塊更新時使用

表級(否)

  詳解讓人困惑的更新鎖

  其它鎖都好了解,唯獨更新鎖讓人迷糊,因為它混合了鎖和标記,在更新前我們首先要讀取記錄,在讀取期間鎖是共享的,而在真正更新時,我們需要獨占鎖,更新鎖是非常短暫的。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

 圖 9 更新時用到的幾種鎖

  不同隔離級别之間的差異,以及何時使用它們

  有4種事務隔離級别,下表列出了這4種隔離級别及其使用時間。

隔離級别

更新

插入

讀未送出的

讀取未送出的資料

允許

讀已送出的(預設)

讀取已送出的資料

重複讀

不允許

序列化

  如何指定隔離級别?

  隔離級别是關系資料庫的一個功能,也就是說,它基本上隻與SQL Server相關,而與ADO.NET,EF或LINQ是沒有什麼關系的,但你可以在這些元件上設定隔離級别。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

圖 10 隔離級别

  中間層:在中間層,你可以使用事務範圍對象指定隔離級别。 

TransactionOptions TransOpt = New TransactionOptions();

TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;

using(TransactionScope scope = new

TransactionScope(TransactionScopeOption.Required, TransOptions))

{

  ADO.NET:在ADO.NET中你可以使用SqlTransaction對象指定事務隔離級别。

SqlTransaction objtransaction =

objConnection.BeginTransaction(System.Data.IsolationLevel.Serializable);

  SQL Server:你也可以在TSQL中使用“SET TRANSACATION ISOLATION LEVEL”指定隔離級别。

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

  事務隔離級别與它能解決的并發性問題之間的對應關系

讀已送出的

能解決

不能

丢失更新

非重複讀

幻想行

  解決方案4:使用“讀已送出的”解決“髒讀取”問題

  關于“讀已送出的”的一些關鍵點:

  •它是SQL Server預設的事務隔離級别。

  •它隻讀取已送出的資料,換句話說就是,任何未送出的資料它都會置之不理,直到資料送出為止,下圖對其進行了詳細解釋,你也可以看到更新。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  圖 11 讀已送出模式解析

  如果你想看到上圖所述的情況,隻需要按照下面的步驟做就可以了:

  •打開兩個查詢視窗,執行一個更新事務,但不送出;

  •在第二個視窗中執行查詢時,會顯示如下圖所示的查詢被阻止的提示。

一起談.NET技術,.NET中鎖6大處理方法 悲觀樂觀自己掌握

  圖 12 查詢被阻止,直到更新事務送出後才能執行

  “讀已送出的”對立面是“讀未送出的”嗎?

  是的,讀未送出的是讀已送出的對立面,當你設定讀未送出的事務隔離級别時,未送出的資料也被讀取了。

  關于“讀已送出的”關鍵點:

  •未送出的是可見的,是以髒讀取是可能的;

  •沒有鎖被抓住;

  • 當鎖不重要時很有用,更重要的是并發性和吞吐量。

  如果你也想測試一下,試試下面的SQL語句,它執行一個更新然,在等待20秒後復原,在此期間如果你執行查詢,傳回的是未送出的資料,但20秒後,你再查詢,傳回的将會以前的舊資料,因為送出的資料已復原。 

set transaction isolation level read uncommitted

Begin Tran

Update customer

set CustomerName='Changed'where CustomerCode='1001'

WAITFOR DELAY '000:00:20'

rollback transet transaction isolation level read uncommitted

select * from Customer where CustomerCode='1001'

  解決方案5:使用重複讀解決丢失更新和非重複讀

  給重複讀設定隔離級别後,其他人就不能讀取和更新資料,關于重複讀隔離級别的關鍵點包含:

   •當為查詢設定重複事務隔離級别時,隻讀取已送出的資料。

   •當你使用重複讀選擇一條記錄時,其它事務将不能更新該條記錄,但查詢是可以的。

  •如果在更新查詢中設定了可重複事務,必須要等到事務完成才能讀和更新相同的記錄。

  • 當選擇和更新查詢被設定為可重複讀,其它事務可以插入新記錄,換句話說就是虛幻行是可能的。

  如果你想測試這個隔離級别,執行下面的語句,然後嘗試查詢和更新查詢,它們都将被阻止,50秒後你才能看到資料。 

set transaction isolation level repeatable read

Update customer set CustomerName='Changed'where CustomerCode='1001'

WAITFOR DELAY '000:00:50'

rollback tran

  如果在重複讀模式下執行下面的查詢語句,在50秒内你啥也幹不了,直到事務完成後你才能得到查詢結果。

begin tran

commit tran

  注意,在此期間你可以添加CustomerCode=’1001’的新記錄,換句話說就是虛幻行是可能的。

  解決方案6:使用序列化隔離級别解決虛幻行問題

  這是最進階的隔離級别,在此期間,其它事務是不能更新,查詢和插入記錄的,關于序列化事務的一些關鍵點包含:

  •當隔離級别是序列化時,沒有其它事務可以插入,更新,删除或查詢。

  •會出現許多阻塞,但所有并發性問題都能得到解決。