本節內容
什麼是並發控制?
悲觀並發控制(Pessimistic Concurrency)
樂觀並發控制(Optimistic Concurrency)
NHibernate支持樂觀並發控制
實例分析
結語
什麼是並發控制?
當許多人試圖同時修改數據庫中的數據時,必須實現一個控制系統,使一個人所做的修改不會對他人所做的修改產生負面影響。這稱為並發控制。
簡單的理解就是2個或多個用者同時編輯相同的數據。這裡的用者可能是:實際用戶、不同服務、不同的代碼段(使用多線程),及其在斷開式和連接式情況下可能發生的情況。
並發控制理論根據建立並發控制的方法而分為兩類:
悲觀並發控制(Pessimistic Concurrency)
一個鎖定系統,可以阻止用戶以影響其他用戶的方式修改數據。如果用戶執行的操作導致應用了某個鎖,只有這個鎖的所有者釋放該鎖,其他用戶才能執行與該鎖沖突的操作。這種方法之所以稱為悲觀並發控制,是因為它主要用於數據爭用激烈的環境中,以及發生並發沖突時用鎖保護數據的成本低於回滾事務的成本的環境中。
簡單的理解通常通過“獨占鎖”的方法。獲取鎖來阻塞對於別的進程正在使用的數據的訪問。換句話說,讀者和寫者之間是會互相阻塞的 ,這可能導致數據同步沖突。
樂觀並發控制(Optimistic Concurrency)
在樂觀並發控制中,用戶讀取數據時不鎖定數據。當一個用戶更新數據時,系統將進行檢查,查看該用戶讀取數據後其他用戶是否又更改了該數據。如果其他用戶更新了數據,將產生一個錯誤。一般情況下,收到錯誤信息的用戶將回滾事務並重新開始。這種方法之所以稱為樂觀並發控制,是由於它主要在以下環境中使用:數據爭用不大且偶爾回滾事務的成本低於讀取數據時鎖定數據的成本。
(以上摘自SQL Server2008 MSDN文檔)
NHibernate支持樂觀並發控制
NHibernate提供了一些方法來支持樂觀並發控制:在映射文件中定義了<version> 節點和<timestamp>節點。其中<version> 節點用於版本控制,表明表中包含附帶版本信息的數據。<timestamp>節點用於時間截跟蹤,表明表中包含時間戳數據。時間戳本質上是一種對樂觀鎖定不是特別安全的實現。但是通常而言,版本控制方式是首選的方法。當然,有時候應用程序可能在其他方面使用時間戳。
下面用兩幅圖顯示這兩個節點映射屬性:
看看它們的意義:
access(默認為property):NHibernate用於訪問特性值的策略。
column(默認為特性名):指定持有版本號的字段名或者持有時間戳的字段名。
generated:生成屬性,可選never和always兩個屬性。
name:持久化類的特性名或者指定類型為.NET類型DateTime的特性名。
type(默認為Int32):版本號的類型,可選類型為Int64、Int32、Int16、Ticks、Timestamp、TimeSpan。注意:<timestamp>和<version type="timestamp">是等價的。
unsaved-value(在版本控制中默認是“敏感”值,在時間截默認是null):表示某個實例剛剛被實例化(尚未保存)時的版本特性值,依靠這個值就可以把這種情況和已經在先前的會話中保存或裝載的游離實例區分開來。(undefined指明使用標識特性值進行判斷)
實例分析
下面用一個例子來實現樂觀並發控制,這裡使用Version版本控制。
1.修改持久化Customer類:添加Version屬性
public class Customer
{
public virtual int CustomerId { get; set; }
//版本控制
public virtual int Version { get; set; }
public virtual string Firstname { get; set; }
public virtual string Lastname { get; set; }
}
2.修改映射文件:添加Version映射節點
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name ="DomainModel.Entities.Customer,DomainModel" table="Customer">
<id name="CustomerId" column="CustomerId" type="Int32" unsaved-value="0">
<generator class ="native"></generator>
</id>
<version name="Version" column="Version" type="integer" unsaved-value="0"/>
<property name="Firstname" column ="Firstname" type="string" length="50" not-null="false"/>
<property name ="Lastname" column="Lastname" type="string" length="50" not-null="false"/>
</class>
</hibernate-mapping>
3.修改數據庫,添加Version字段
具體參數:[Version] [int] NOT NULL 默認值為1,當然了修改數據庫是最原始的方式了,如果你會使用SchemaExport,可以直接利用持久化類和映射文件生成數據庫,以後在介紹如何使用這個。
4.並發更新測試
在測試之前,我們先看看數據庫中什麼數據,預知一下:
編寫並發更新測試代碼:
查詢2次CustomerId為1的客戶,這裡就是上面的第一條數據,第一個修改為"CnBlogs",第二個修改為"www.cnblogs.com",兩者同時更新提交。你想想發生什麼情況?
[Test]
public void UpdateConcurrencyViolationCanotThrowException()
{
Customer c1 = _transaction.GetCustomerById(1);
Customer c2 = _transaction.GetCustomerById(1);
c1.Name.Firstname = "CnBlogs";
c2.Name.Firstname = "www.cnblogs.com";
_transaction.UpdateCustomerTransaction(c1);
_transaction.UpdateCustomerTransaction(c2);
}
讓我們去看看數據庫吧,一目了然:
我們發現CustomerId為1的客戶更新了FirstName數據,並且Version更新為2。你知道什麼原理了嗎?看看這步NHibernate生成的SQL語句(我的可能比你的不一樣):先查詢數據庫,在直接更新數據,看看NHibernate多麼實在,明顯做了一些優化工作。
SELECT customer0_.CustomerId as CustomerId3_0_,
customer0_.Version as Version3_0_,
customer0_.Firstname as Firstname3_0_,
customer0_.Lastname as Lastname3_0_,
customer0_1_.OrderDiscountRate as OrderDis2_4_0_,
customer0_1_.CustomerSince as Customer3_4_0_,
case
when customer0_1_.CustomerId is not null then 1
when customer0_.CustomerId is not null then 0
end
as clazz_0_ FROM Customer
customer0_ left outer join PreferredCustomer customer0_1_
on customer0_.CustomerId=customer0_1_.CustomerId
WHERE customer0_.CustomerId=@p0; @p0 = '1'
UPDATE Customer SET Version = @p0, Firstname = @p1, Lastname = @p2
WHERE CustomerId = @p3 AND Version = @p4;
@p0 = '2', @p1 = 'www.cnblogs.com', @p2 = 'Lee', @p3 = '1', @p4 = '1'
5.並發刪除測試
我們再來編寫一個測試用於並發刪除。查詢2次CustomerId為2的客戶,這裡就是上面的第二條數據,兩者同時刪除數據。你想想發生什麼情況?
[Test]
[ExpectedException(typeof(NHibernate.StaleObjectStateException))]
public void DeleteConcurrencyViolationCanotThrowException()
{
Customer c1 = _transaction.GetCustomerById(2);
Customer c2 = _transaction.GetCustomerById(2);
_transaction.DeleteCustomerTransaction(c1);
_transaction.DeleteCustomerTransaction(c2);
}
同理,看看數據庫裡的數據,第二條數據不見了。
其生成SQL的查詢語句同上面一樣,只是一條刪除語句:
DELETE FROM Customer WHERE CustomerId = @p0 AND Version = @p1; @p0 = '2', @p1 = '1'
好了,這裡通過兩個簡單的實例說明了在NHibernate中對並發控制的支持。相信有了一定的了解,大家也可以編寫一些有趣的測試來試試NHibernate中的樂觀並發控制。
結語
這一篇我們初步探索了NHibernate中的並發控制,並用一個典型的實例分析了具體怎麼做。我想這只是蜻蜓點水,更多的樂趣就自己探索吧。比如在不同的Session中的並發啊,更新啊,刪除啊......