.NET中鎖6大處理方法 悲觀樂觀自己掌握

來源: IT168  發布時間: 2010-10-24 23:06  閱讀: 4669 次  推薦: 1   原文鏈接   [收藏]  
摘要:本文介紹了處理.NET中鎖的6種方法,首先我們討論一下并發性問題,然后討論處理樂觀鎖的3種方法,樂觀鎖不能從根源上解決并發問題,因此后面我們介紹了悲觀鎖,最后介紹隔離級別如何幫助我們實現悲觀鎖,每個隔離級別都列舉了示例進行說明,使得概念更加清晰。

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

  我們為什么需要鎖?

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

我們為什么需要鎖?

  圖 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丟失了他的更新

  如何解決上述沖突?

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

我們為什么需要鎖?

  圖 2 樂觀鎖和悲觀鎖  

  什么是樂觀鎖?

  顧名思義,樂觀鎖假設多個事務相互不會影響對方,換句話說就是,在樂觀鎖模式下,沒有鎖操作會得到執行,事務只是驗證是否有其它事務修改數據,如果有則進行事務回滾,否則就提交。

處理.NET中鎖的6種方法

 圖 3 樂觀鎖

  樂觀鎖是如何工作的?

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

  •記錄當前的時間戳

  •開始修改值

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

  • 如果不相等就回滾,否則就提交

處理.NET中鎖的6種方法

  圖 4 樂觀鎖的工作原理

  實現樂觀鎖的解決方案

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

  •數據集(Dataset):數據集是實現樂觀鎖的默認方法,在更新前它會檢查新舊值。

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

  •直接檢查新舊值:在更新時檢查舊值和新值是否相等,如果不相等就回滾,否則就提交。

  解決方案1:數據集

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

處理.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數據時,時間戳會自動產生一個唯一的二進制數值,時間戳數據類型可用來版本化你的記錄更新。

1

 圖 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時,你應該會看到錯誤傳播。

1

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

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

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

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

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

  什么是悲觀鎖?

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

1

圖 8 悲觀鎖

  如何處理悲觀鎖?

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

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

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

  何時使用 允許讀 允許寫
共享鎖 當你只想讀,不希望其它事務進行更新時
獨占鎖 當你想修改數據,同時不希望別人可以讀,直到你更新完畢時
更新鎖 這是一個混合鎖,當你執行的更新操作有多個步驟時使用    
讀階段
操作階段
更新階段
意向鎖(請求鎖) 意向鎖是分級的,當你想鎖定下級資源時使用,例如,在表上的一個共享意向鎖意味著共享鎖是針對頁面和表中的行的, 不適用 不適用
模式鎖 當你修改表結構時使用
大數據塊更新鎖 當你執行大數據塊更新時使用 表級(否) 表級(否)

 

  詳解讓人困惑的更新鎖

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

1

 圖 9 更新時用到的幾種鎖

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

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

隔離級別 更新 插入
讀未提交的 讀取未提交的數據 允許 允許
讀已提交的(默認) 讀取已提交的數據 允許 允許
重復讀 讀取已提交的數據 不允許 允許
序列化 讀取已提交的數據 不允許 不允許

  如何指定隔離級別?

  隔離級別是關系數據庫的一個功能,也就是說,它基本上只與SQL Server相關,而與ADO.NET,EF或LINQ是沒有什么關系的,但你可以在這些組件上設置隔離級別。

1

圖 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默認的事務隔離級別。

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

1

  圖 11 讀已提交模式解析

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

  •打開兩個查詢窗口,執行一個更新事務,但不提交;

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

1

  圖 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
Begin Tran
Update customer
set CustomerName='Changed'where CustomerCode='1001'
WAITFOR DELAY '000:00:50'
rollback tran

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

 
set transaction isolation level repeatable read
begin tran
select
* from Customer where CustomerCode='1001'
WAITFOR DELAY '000:00:50'
commit tran

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

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

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

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

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

1
0
 
標簽:.NET ADO.NET
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()