標籤:err obj 查看 gray http date 第一個 教程 tor
本節內容
- 什麼是並發控制?
- 封閉式並行存取控制(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中的並發啊,更新啊,刪除啊......
NHibernate教程(7)--並發控制