Entity Framework 樂觀並發控制


一、背景

  我們知道,為了防止並發而出現臟讀臟寫的情況,可以使用Lock語句關鍵字,這屬於悲觀並發控制的一種技術,,但在分布式站點下,鎖的作用幾乎不存在,因為雖然鎖住了A服務器的實例對象,但B服務器上的鎖是不知道的A服務器上鎖的情況的,所以,面對分布式站點、單一數據庫這種架構,我們可以使用EntityFramework的樂觀並發控制來解決這個問題,EF對並發控制有不管控和樂觀並發控制兩種,默認情況是不管控,但EF不支持悲觀並發。
lock 確保當一個線程位於代碼的臨界區時,另一個線程不進入臨界區。如果其他線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。
 

二、悲觀並發和樂觀並發

悲觀並發:比如有兩個用戶A,B,同時登錄系統修改一個文檔,如果A先進入修改,則系統會把該文檔鎖住,B就沒辦法打開了,只有等A修改完,完全退出的時候B才能進入修改。

樂觀並發:同上面的例子,A,B兩個用戶同時登錄,如果A先進入修改緊跟着B也進入了。A修改文檔的同時B也在修改。如果在A保存之后B再保存他的修改,此時系統檢測到數據庫中文檔記錄與B剛進入時不一致,B保存時會拋出異常,修改失敗。

樂觀並發的基本出發點是:當保存數據的時候抱着一種樂觀的態度,不期望發生並發沖突,即使萬一發生並發沖突,也能捕捉到沖突異常,然后根據策略解決沖突,而解決沖突的方式一般分為Client wins(以后操作者為贏) 和 Store wins(以先存儲的數據為贏)。

 

三、EF中如何控制並發

第1步、
  在設計器中,對需要進行並發控制的字段的ConcurrencyMode並發模式設置為Fixed, 這是檢測是否發生沖突的指標,該字段最終會在EF生成SQL時的where子句出現,如果沒有設置為Fixed,即使該字段出現並發沖突,EF也不會報出並發異常,從而會導致出現臟讀臟寫的情況。
 
 
 
第2步、

 Resolving optimistic concurrency exceptions with Reload

  使用Reload數據作為解決樂觀並發異常的策略之一,我在這里就講數據Reload這一種策略就好了,除了Reload外,還有其他幾種沖突解決策略,詳見參考文獻中EF官方團隊博客。

  微軟Entity Framework 團隊官方博客 推薦處理樂觀並發沖突的策略之一是Reload數據,也就是EF檢測到並發沖突時會拋出DbUpdateConcurrencyException,這時解決沖突分為Client Wins或者Store Wins ,而Reload處理也就是Store Wins,意味着放棄當前內存中的實體,重新到數據庫中加載當前實體,EF官方團隊給出來的示例代碼如下,其他幾種策略請見參考文獻鏈接。 
 
using (var context = new UnicornsContext())
{
    bool saveFailed;
    do
    {
        saveFailed = false; var unicorn = context.Unicorns.Find(1);
        unicorn.Name = "tom";
        try
        {
            context.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            saveFailed = true; // Update the values of the entity that failed to save 
            // from the store
 ex.Entries.Single().Reload();
        }
    } while (saveFailed);
}

 

四、SqlProfiler看原理

  完成以上步驟后,您的程序就具備了樂觀並發處理的能力了,但是,其中的原理是什么呢?從SqlProfiler監控結果來看,EF是將並發模式設置為Fixed的字段放在where子句里,后操作會因為取不到相應的記錄而更新失敗,打個比方說,當兩個用戶同時對同一條記錄(ID=1,Count=10)進行讀、寫操作的時候,A、B同時讀取記錄的時候,Count都等於10,A用戶先進行Update減1操作,此時(ID=1,Count=9),而此時B用戶稍微晚一點點再進行Update操作的時候,因為Count已經被A修改成9了,已經不存在(ID=1,Count=10)的記錄了,所以B最終執行影響行數為0,EntityFramework拋出並發異常:


如:我們給Count屬性的並發模式設置成Fixed的話,那生成的SQL語句如下:

 
         
exec sp_executesql N'update [dbo].[OrderLog]
set [Count] = @0
where (([ID] = @1) and ([Count] = @2))',N'@0 int,@1 int,@2 int',@0=78,@1=1,@2=9 

 

當EntityFramework執行更新操如果影響行數為0,就會拋出異常,相關源代碼如下所示:

 


 

五、總結

   個人認為,樂觀並發控制適用於並發量還不是很大情況,也就是符合樂觀並發的初衷,當保存數據的時候不期望發生並發沖突,一旦發生沖突,也能捕捉到沖突異常,然后根據策略解決沖突或者提示用戶操作失敗等,但是,當並發量很大的時候,我認為樂觀並發就顯得並不那么適用了,個人建議,做評估項目並發風險和做並發沖突的測試,假如完全可以接受,大可以應用EntityFramework的樂觀並發控制,實現起來也比較簡單,假如項目並發量確實很大,那可以考慮別的技術方案實現,比如消息隊列……等。

 

參考文獻

(1)微軟EntityFramework團隊博客: Using DbContext in EF 4.1 Part 9: Optimistic Concurrency Patterns

(2)Gyoung: Entity Framework 並發處理

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM