標籤:style blog http color 使用 os strong io
一、背景 我們知道,為了防止並發而出現髒讀髒寫的情況,可以使用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] = @0where (([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 並發處理