提到數據庫操作特別是企業級的數據庫應用就不得不提一個多人操作時經常會產生的問題——並發沖突本文首先來看一下什麼是並發沖突傳統的並發沖突有現有的處理方式最後結合EF看一個處理並發沖突的實例
一要完成本文中的實例您需要作如下准備
將Visual Studio 及NET Framework 升級到SP點擊轉到升級地址
安裝SQL SERVER VS 中自帶的EXPRESS版的SQL SERVER應該也可以用
下載並附加數據庫點擊下載DemoDbV
創建一個VB Console Application並且取一個合適的名字(例如Concurrency之類的)注意目標Framework要設置成版
二什麼是並發沖突
讓我們來看一個跟取款相關的例子某年某月某日某時某分老王在A取款機取錢他兒子小王同時在B取款機取錢(不要問我為什麼這麼巧^_^)他倆從同一個賬號上取於是就發生了如下一序列的操作
A取款機向中央數據庫提問這賬上還有多少錢?
B取款機向中央數據庫詢問這賬上還有多少錢?
中央數據庫回答A取款機W
中央數據庫回答B取款機W
然後老王對A取款機說我要取出W
同時小王對B取款機說我要取出W
A取款機就算了一下WW=W>於是就吐出W現金給了老王並且准備告訴中央數據庫現在還剩W啦但是就在它告訴中央數據庫之前發生了以下的事情
B取款機計算了一下W(此時它還不知道余額已經成W了因為A取款機還沒有告訴中央數據庫)減去W等於W大於於是就吐出W現金給了小王然後它當然也要知會中央數據庫
中央數據庫於是收到A取款機的消息說這個賬號還剩W於是刷新余額為W然後又收到B取款機說還剩W於是就刷新余額為W
呵呵於是小王+老王的賬戶裡一共存有W元結果老王取了W元小王取了W元賬戶裡卻還剩了W元!~@#$%^&
這就是一種並發沖突由於同一時間有兩個或者多個端在對同一數據進行操作從而導致數據發生了錯誤如果取款機真的以這樣的方式來處理並發那麼我現在就不寫這片文章了——趕緊發動全家對表說好了在某一時刻同時取錢去^_^
三
常見的並發沖突處理方式
一般來說
我們把並發沖突處理方式歸結為
類
第一類
放任不管方式
第二類
開放式並發處理方式
第三類
保守式並發處理方式
放任不管方式
與其說這是一種處理並發沖突的方式
不如說
它是一種沒有對並發沖突做任何處理的方式
但是在許多過去的系統裡
由於沒有考慮到多用戶
網絡應用等情況
這種
處理方式
還真存在於不少系統中
舉例來說
A
B兩人從數據庫中獲取了同一個筆記本的信息
例如
IBM ThinkPad T
吧
然後
A把牌子改成了
Lenovo ThinkPad
B把型號改成了T
A
然後
他們開始提交了
此時
如果A先提交
然後B提交
那麼
最後的結果是
IBM ThinkPad T
A
反之
則變成Lenovo ThinkPad T
總之一句話
誰最後提交誰老大
想像一下
如果A修改了
個屬性的值
B修改了
個屬性的值
那麼
對於先提交的A來說
這將是一個多麼慘痛的打擊:
)
雖然這種放任不管的方式似乎不太負責任
但是
其處理性能卻是相對較高的
開放式並發處理
開放式並發處理
老外叫做Optimistic Concurrency——樂觀的並發
這種並發處理方式要求我們對並發抱有一種樂觀的態度
百分之九十九點九九不會發生並發沖突
萬一發生了
系統也能捕獲到沖突
或者根據策略自動處理
或者
就提醒一下用戶
讓用戶來決定是不是要繼續提交
仍然用上面的例子來說這事兒
A
B兩個人同時獲取了筆記本的信息
IBM ThinkPad T
然後……(此處跟上例做一樣的修改
直到提交)此時
如果A先提交
那麼
B提交的時候
系統會發現
哎喲
不好
有並發沖突了
就會拋個異常給B
讓B知道
發生並發沖突了
然後
B就可以根據實際情況
選擇相應的處理策略(比如
繼續提交進行覆蓋或者取消提交等等)
相反
如果B先提交
那麼
A提交時
就會得到相應的提醒
這樣的並發處理方式
可以說在可靠性與性能上取得平衡
適合於對數據可靠性要求不是特別嚴格
需要較高的性能
並且不會大量發生並發的場合
保守式並發處理
這是最為嚴謹的一種並發沖突的處理方式
它把並發轉化為了串行操作
例如
A從數據庫中獲取了筆記本信息
IBM ThinkPad T
B也要對其進行修改
但此時由於A已經從數據庫中將數據取出
因此
B被置於等待狀態
直到A把數據修改完提交了
數據庫數據更新為Lenovo ThinkPad T
了
此時
數據庫才把數據給B
那麼B就可以在Lenovo ThinkPad T
的基礎上
把它修改為Lenovo ThinkPad T
A
而在B提交前
其它一切針對此記錄的操作都得排除等著B
這樣子當然非常理想
由於不存在並發
自然也就消除了並發沖突的問題
但是
這種鎖也存在著較為隱蔽的風險
如果A修改了數據
一直不提交
或者A因為故障
沒有辦法提交
那麼
其它所有的相關的操作
都將被阻礙住
因此
只有對數據准確性要求極高並且用戶可以忍受等待的情況下
使用這種並發沖突的處理方法
四
EF並發沖突處理實例
EF發布時
提供了兩種並發沖突處理方式
放任不管方式和開放式並發
默認采用放任不管的方式處理
如果要使用開放式並發
那麼
必須設置相應屬性上的Concurrency Mode值為Fixed
我們先對實體類的屬性進行修改
讓其支持開放式並發
然後來模擬一個並發的序列
看看怎麼來處理並發沖突
當前數據庫情況如圖
所示
第一步
在概念模型設計器裡
按照圖
所示
分別把Notebook
Brand和Notebook
Type屬性的Concurrency Mode設置為Fixed
然後
我們寫一段代碼來模擬一個並發沖突的情況
Create object context
Dim objContext
As New NbWhEntities()
Dim objContext
As New NbWhEntities()
Query the same record as
entities
Dim laptop
= (From aLaptop In objContext
Notebook _
Where aLaptop
Id =
_
Select aLaptop)
FirstOrDefault()
Dim laptop
= (From aLaptop In objContext
Notebook _
Where aLaptop
Id =
_
Select aLaptop)
FirstOrDefault()
Modify the entity
laptop
Brand =
Lenovo ThinkPad
laptop
Type = laptop
Type &
A
Submit
st object context
objContext
SaveChanges()
Try
Submit
nd object context and cause cuncurrency exception
objContext
SaveChanges()
Catch ex As OptimisticConcurrencyException
Using refresh method to
objContext
Refresh(Objects
RefreshMode
StoreWins
laptop
)
We should load the new data from db and ask user to change it again
objContext
SaveChanges()
End Try
我們創建了兩個Object Context
分別查詢出了同一個實體
第一個修改其品牌為
Lenovo ThinkPad
第二個同時將其型號修改為
T
A
然後
第一個實體保存
然後
第二個保存
由於我們在Brand屬性上設置了Concurrency Mode為Fixed
而此時
laptop
中的Brand屬性的值應該是一開始取得的T
而數據庫裡的值是
Lenovo T
於是
系統就會拋出
OptimisticConcurrencyException
(開放式並發異常)
當程序捕獲到異常以後
就可以使用Object Context的Refresh方法對異常采取處理
由於沒有在刷新laptop
以後
未對其作任何修改
故最終結果將與laptop
提交時的結果一致
這裡
Refresh的第一個參數值得注意一下
它是一個枚舉值
有兩個選項
StoreWins或者是ClientWins
見名知義
如果是StoreWins
那麼
Refresh以後
laptop
的值將與數據庫裡的對應記錄的值一致(修改會丟失)
而如果ClientWins
則laptop
的值保持
並且提交以後
會把objContext
提交的修改覆蓋
其實
這兩種方法均不完美
總會導致一部分修改丟失
但是
這總比在不知情的情況下的覆蓋要好
另外
需要說明
上面的方法
只是對並發沖突的一種模擬
這樣的模式
在處理並發沖突時會有問題
一般的處理方法是
當檢測到並發沖突時
提示用戶會重新從數據庫載入數據
然後
讓用戶在新數據的情況下重新修改後再次提交
直到不再有並發沖突發生
這樣
看似可能成為一個無窮盡的痛苦的過程
但實際上
由於這種處理方式是基於對並發沖突的樂觀估計來設計的
因此
當我們認為並發沖突很少有可能發生時
這種處理方式可以有效避免數據被無意識的覆蓋問題
五
示例代碼下載
點擊下載
From:http://tw.wingwit.com/Article/program/net/201311/11293.html