目錄
樂觀並發控制(Optimistic Concurrency)
悲觀並發控制(Pessimistic Concurrency)
寫在前面
上篇文章介紹了nhibernate中的事務,在增刪改查中使用的必要性。本篇文章將介紹nhibernate中的並發控制。對多人同時修改同一條數據,如何進行並發控制,在nhibernate中提供了一些方法來實現樂觀並發控制。
文檔與系列文章
[NHibernate]持久化類(Persistent Classes)
[NHibernate]集合類(Collections)映射
[NHibernate]緩存(NHibernate.Caches)
[NHibernate]NHibernate.Tool.hbm2net
[NHibernate]Nhibernate如何映射sqlserver中image字段
[NHibernate]條件查詢Criteria Query
並發控制
什么是並發控制?
當很多人試圖同時修改數據庫中的數據時,必須有這樣一種控制,使一個人的操作不對他人的操作產生負面影響,這就是並發控制。
說的更簡單點就是,2個或者多個用戶(實際用戶,服務,多線程)同時編輯相同數據時,及其在連接或者斷開情況下可能發生的情況。
並發控制理論根據控制方法而分為兩類:樂觀並發控制和悲觀並發控制。
樂觀並發控制(Optimistic Concurrency)
在樂觀並發控制中,用戶讀取數據時不鎖定數據。當一個用戶更新數據時,系統將進行檢查,查看該用戶讀取數據后其他用戶是否又更改了該數據。如果其他用戶更新了數據,將產生一個錯誤。一般情況下,收到錯誤信息的用戶將回滾事務並重新開始。這種方法之所以稱為樂觀並發控制,是由於它主要在以下環境中使用:數據爭用不大且偶爾回滾事務的成本低於讀取數據時鎖定數據的成本。(sql2008 MSDN)
NHibernate提供了一些方法實現樂觀並發控制,在配置文件中:定義了<version>和<timespan>節點,其中<version>節點用於版本控制,表明數據表中數據的版本信息。<timespan>用於時間戳跟蹤,表明數據表中包含時間戳數據。時間戳本質上是一種樂觀鎖定不太安全的實現。通常而言,版本控制方式是首選的方式。
看一下映射文件中version和timestamp節點的屬性:

屬性說明:
access(默認為property):Nhibernate用於訪問特性值的策略。
column(默認為特性名):指定具有版本號或者時間戳的字段名。
gennerated:生成屬性,可選never和always兩個值。
name:持久化類的特性名或者指定類型為.NET類型DateTime的特性名。
type(默認Int32):版本號的類型,可選類型為Int64、Int32、Int16、Ticks、Timestamp、TimeSpan。注意:<timestamp>和<version type="timestamp">是等價的。
unsaved-value(在版本控制中默認是“敏感”值,在時間截默認是null):表示某個實例剛剛被實例化(尚未保存)時的版本特性值,依靠這個值就可以把這種情況和已經在先前的會話中保存或裝載的游離實例區分開來。(undefined指明使用標識特性值進行判斷).
一個例子
使用版本控制方式進行樂觀並發控制,修改持久化類Customer,添加版本控制屬性Version
1 /// <summary> 2 /// 描述:客戶實體,數據庫持久化類 3 /// 創建人:wolfy 4 /// 創建時間:2014-10-16 5 /// </summary> 6 public class Customer 7 { 8 /// <summary> 9 /// 客戶id 10 /// </summary> 11 public virtual Guid CustomerID { get; set; } 12 /// <summary> 13 /// 客戶名字 14 /// </summary> 15 public virtual string CustomerName { get; set; } 16 /// <summary> 17 /// 版本控制 18 /// </summary> 19 public virtual int Version { get; set; } 20 /// <summary> 21 /// 客戶地址 22 /// </summary> 23 public virtual string CustomerAddress { get; set; } 24 }
修改映射文件Customer.hbm.xml,添加version映射節點。
1 <?xml version="1.0" encoding="utf-8" ?> 2 <!--assembly:程序集,namespace:命名空間--> 3 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Wolfy.Shop.Domain" namespace="Wolfy.Shop.Domain.Entities"> 4 <class name="Wolfy.Shop.Domain.Entities.Customer,Wolfy.Shop.Domain" table="TB_Customer"> 5 <!--主鍵--> 6 <id name="CustomerID" type="Guid" unsaved-value="null"> 7 8 <column name="CustomerID" sql-type="uniqueidentifier" not-null="true" unique="true"/> 9 <generator class="assigned"></generator> 10 </id> 11 <!--版本控制--> 12 <version name="Version" column="Version" type="integer" unsaved-value="0"/> 13 <property name="CustomerName" type="String"> 14 <column name="CustomerName" sql-type="nvarchar" not-null="false"/> 15 </property> 16 <property name="CustomerAddress" type="String"> 17 <column name="CustomerAddress" sql-type="nvarchar" not-null="false"/> 18 </property> 19 </class> 20 </hibernate-mapping>
修改數據表TB_Customer,添加version字段,默認值為1。
alter table tb_customer add [Version] int not null default 1
並發更新控制
數據庫中的數據有

測試同時修改客戶id為“82724514-682E-4E6F-B759-02E499CDA50F”名字為“wanger”的客戶信息,兩個操作同時修改客戶住址為“南京”和“黑龍江”。代碼如下:
數據層代碼
1 /// <summary> 2 /// 通過事務的方式添加或者修改 3 /// </summary> 4 /// <param name="customer">添加的對象</param> 5 /// <returns>是否成功</returns> 6 public bool SaveOrUpdateByTrans(Customer customer) 7 { 8 NHibernateHelper nhibernateHelper = new NHibernateHelper(); 9 var session = nhibernateHelper.GetSession(); 10 using (ITransaction transaction = session.BeginTransaction()) 11 { 12 try 13 { 14 session.SaveOrUpdate(customer); 15 session.Flush(); 16 //成功則提交 17 transaction.Commit(); 18 return true; 19 } 20 catch (Exception) 21 { 22 //出現異常,則回滾 23 transaction.Rollback(); 24 return false; 25 } 26 } 27 }
測試數據
1 /// <summary> 2 /// 並發更新操作 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 protected void btnSameTimeUpdate_Click(object sender, EventArgs e) 7 { 8 Guid guidCustomerId = new Guid("82724514-682E-4E6F-B759-02E499CDA50F"); 9 //模擬第一個修改數據 10 Customer c1 = new Customer() 11 { 12 CustomerID = guidCustomerId, 13 CustomerAddress = "南京", 14 CustomerName = "wanger", 15 Version = 1 16 }; 17 //模擬第二個修改數據 18 Customer c2 = new Customer() 19 { 20 CustomerID = guidCustomerId, 21 CustomerAddress = "黑龍江", 22 CustomerName = "wanger", 23 Version = 1 24 }; 25 26 Business.CustomerBusiness customerBusiness = new Business.CustomerBusiness(); 27 customerBusiness.SaveOrUpdateByTrans(c1); 28 customerBusiness.SaveOrUpdateByTrans(c2); 29 }
修改后的數據
同對比上面的數據庫中的數據,我們發現Version版本變成2了,而且修改的客戶地址為“黑龍江”信息並沒有修改成功。監控的sql發現,兩次update確實都提交了,但是只有第一次修改了。如圖:


細心的你可能會發現@p0的值在改變,並且在修改的時候多了一個Where子句
WHERE CustomerID = @p3 AND Version = @p4'
這就是為什么第二次為什么沒有修改成功了,因為已經找不到這條數據了。
悲觀並發控制(Pessimistic Concurrency)
一個鎖定系統,可以阻止用戶以影響其他用戶的方式修改數據。如果用戶執行的操作導致應用了某個鎖,只有這個鎖的所有者釋放該鎖,其他用戶才能執行與該鎖沖突的操作。這種方法之所以稱為悲觀並發控制,是因為它主要用於數據爭用激烈的環境中,以及發生並發沖突時用鎖保護數據的成本低於回滾事務的成本的環境中。
簡單的理解通常通過“獨占鎖”的方法。獲取鎖來阻塞對於別的進程正在使用的數據的訪問。換句話說,讀者和寫者之間是會互相阻塞的 ,這可能導致數據同步沖突。
總結
本文講述nhibernate中樂觀並發控制的版本控制方式,列舉了一個並發修改的例子(很可能你會說那不是順序執行的嗎?你也看到這種順序執行,也無法修改),在分析生成的sql語句中,你會發現如果加上版本控制,在修改的時候會在where子句中加上版本號。
參考文章:http://www.cnblogs.com/lyj/archive/2008/10/21/1316269.html
