前言:
數據庫并發問題詳述http://www.csdn.net/Develop/read_article.asp?id=24366已經說明了并發的嚴重性與危害性。下面講述VB+ADO來處理并發操作的實際案例:
在以前DAO中可以對數據庫進行記錄鎖,頁面鎖,表鎖來處理并發操作,還可以使用事務處理,那么現在怎么用ADO來檢測并處理數據庫的并發操作呢?
相關背景知識:
ADO中對數據庫的也是采用鎖定的方法來實現的,還可以用事務來做。事務有個特點就是:要么全成功,要么就全失敗。那么在實際工作中有可能只有幾條或一小部分的記錄有沖突,只要對那一小部分的記錄進行處理就行了。ADO也使用來鎖定來實現。
那么什么是鎖定?
鎖定是一種進程,DBMS 通過該進程限制多用戶環境中對行的訪問。當一行或一列被獨占鎖定時,不允許其他用戶在釋放鎖定之前訪問鎖定的數據。這確保了兩個用戶無法同時更新一行中的同一列。
從資源角度而言,鎖定的成本可能非常高昂,只有在需要保持數據完整性的情況下才應當使用此功能。在每秒有數百或數千用戶試圖訪問某個記錄的數據庫(例如連接到 Internet 的數據庫)中,不必要的鎖定將很快導致應用程序性能的下降。
可選擇適當的鎖定選項來控制數據源和 ADO 游標庫管理并發性的方式。
打開 Recordset 之前先設置 LockType 屬性,以指定提供者打開它時使用的鎖定類型。讀取該屬性以返回打開的 Recordset 對象中使用的鎖定類型。
提供者可能不支持所有鎖定類型。如果提供者不支持請求的 LockType 設置,則替換為另一種鎖定類型。若要確定 Recordset 對象中實際可用的鎖定功能,將soppurts 方法與 adUpdate 和 adUpdateBatch 一起使用。
如果 CursorLocation 屬性設置為 adUseClient,則不支持 adLockPessimistic 設置。如果設置了不支持的值,將不產生錯誤,而使用所支持的最相近的 LockType。
LockType 屬性在 Recordset 關閉時為讀/寫,而在 Recordset 打開時為只讀。
鎖定類型種類(LockType 屬性):
1.adLockBatchOptimistic
指示開放式批更新。需要批更新模式。
許多應用程序都一次提取多行,然后需要進行相應的更新,這些更新包括所有需要插入、更新或刪除的行的完整集合。使用批游標只需一次往返服務器,因而導致更新性能的提高和網絡通信量的降低。使用批游標庫可創建靜態游標,然后斷開到數據源的連接。這時就可以對行進行更改,然后重新連接到數據源并將更改以批的形式發布到數據源。
2.adLockOptimistic
提供者使用開放式鎖定,僅在調用 Update 方法時鎖定記錄。這意味著另一個用戶有可能會利用您編輯記錄和調用 Update 的時間間隔更改數據,這就會引起沖突。而使用此鎖定類型時發生沖突的幾率很低,即使發生了沖突也會很快得到解決。
3.adLockPessimistic
指示逐個記錄保守式鎖定。提供者要確保記錄編輯成功,通常在編輯之前立即在數據源鎖定記錄。當然,這意味著一旦您開始編輯記錄,這些記錄就會對其他用戶不可用,知道您通過調用 Update 釋放鎖定。如果您的系統不提供對數據的并發更改,例如預定系統,那么可使用此鎖定類型。
4.adLockReadOnly
指示只讀記錄。無法改變數據。只讀鎖定是速度“最快”的鎖定類型,因為它不要求服務器保持對記錄的鎖定。沖突
5.adLockUnspecified
未指定鎖定類型。
檢測和解決沖突:
如果在立即模式中處理 Recordset,則很少出現并發問題。另一方面,如果應用程序使用批模式更新,則在保存由一個正在編輯記錄的用戶所作的更改之前,編輯同一個記錄的另一個用戶比較有可能會更改記錄。在這種情況下,需要應用程序準確處理沖突。無論是哪種情況,都可以使用 ADO 提供的 Field 對象的 UnderlyingValue 和 OriginalValue 屬性來處理這些類型的沖突。將這些屬性與 Recordset 的 Resync 方法和 Filter 屬性配合使用。
檢測錯誤
在批更新期間 ADO 遇到沖突時,將在 Errors 集合中放入警告。因此,調用 BatchUpdate 之后,一定要立即檢查是否有錯誤,如果找到了錯誤,則應當假設已遇到沖突并開始進行測試。第一步要將 Recordset 的 Filter 屬性設置為等于 adFilterConflictingRecords。該設置將使 Recordset對象限制為只顯示那些發生沖突的記錄。如果這一步之后 RecordCount 屬性等于零,就說明錯誤是由沖突以外的其他原因引起的。
調用 BatchUpdate 時,ADO 和提供者將生成對數據源執行更新的 SQL 語句。下一步,調用 Recordset 的 Resync 方法,并且將 AffectRecords 參數設置為等于 adAffectGroup,將 ResyncValues 參數設置為等于 adResyncUnderlyingValues。Resync 方法將用來自基本數據庫中的數據刷新在當前 Recordset 對象中的數據。通過使用 adAffectGroup,可以確保只有使用當前篩選設置的情況下可見的記錄(即只有沖突記錄)會與數據庫重新同步。如果處理的是大型 Recordset,該操作會對性能有較大影響。通過在調用 Resync 時將 ResyncValues 參數設置為 adResyncUnderlyingValues,可以確保 UnderlyingValue 屬性將包含數據庫中的(沖突)值,并確保 Value 屬性仍然是由用戶輸入的值,還會確保 OriginalValue 屬性將持有字段的原始值(即在上一次成功進行 UpdateBatch 調用之前該字段所擁有的值)。然后,可以使用這些值通過編程方式解決沖突,或要求用戶選擇將要使用的值。
范例:
在調用 UpdateBatch 之前,本范例通過使用單獨的 Recordset 來更改基本表中的值,人為地創建一個沖突。
´Begin
On Error GoTo ErrHandler:
Dim objRs1 As New ADODB.Recordset
Dim objRs2 As New ADODB.Recordset
Dim strSQL As String
Dim strMsg As String
strSQL = "SELECT * FROM Shippers WHERE ShipperID = 2"’SQL查詢
objRs1.CursorLocation = adUseClient’設置客戶端游標
objRs1.Open strSQL, strConn, adOpenStatic, adLockBatchOptimistic, adCmdText’執行查詢生成objrs1記錄集
objRs1("Phone") = "(111) 555-1111"’更改表中一條記錄phone字段的值
objRs2.Open strSQL, strConn, adOpenKeyset, adLockOptimistic, adCmdText’執行查詢生成objrs2記錄集
objRs2("Phone") = "(999) 555-9999" ’更改表中記錄phone字段的值
objRs2.Update
objRs2.Close
Set objRs2 = Nothing
On Error Resume Next
objRs1.UpdateBatch
If objRs1.ActiveConnection.Errors.Count <> 0 Then
Dim intConflicts As Integer
intConflicts = 0
objRs1.Filter = adFilterConflictingRecords
intConflicts = objRs1.RecordCount
objRs1.Resync adAffectGroup, adResyncUnderlyingValues
If intConflicts > 0 Then
strMsg = "A conflict oclearcase/" target="_blank" >ccurred with updates for " & intConflicts & "records." & vbCrLf & "The values will be restored" " to their original values."
objRs1.MoveFirst
While Not objRs1.EOF
strMsg = strMsg & "SHIPPER = " & objRs1("CompanyName") & vbCrLf
strMsg = strMsg & "Value = " & objRs1("Phone").Value & vbCrLf
strMsg = strMsg & "UnderlyingValue = " & _
objRs1("Phone").UnderlyingValue & vbCrLf
strMsg = strMsg & "OriginalValue = " & _
objRs1("Phone").OriginalValue & vbCrLf
strMsg = strMsg & vbCrLf & "Original value has been restored."
MsgBox strMsg, vbOKOnly, _
"Conflict " & objRs1.AbsolutePosition & _
" of " & intConflicts
objRs1("Phone").Value = objRs1("Phone").OriginalValue
objRs1.MoveNext
Wend
objRs1.UpdateBatch adAffectGroup
Else
strMsg = "Errors occurred during the update. " & _
objRs1.ActiveConnection.Errors(0).Number & " " & _
objRs1.ActiveConnection.Errors(0).Description
End If
On Error GoTo 0
End If
objRs1.MoveFirst
objRs1.Close
Set objRs1 = Nothing
Exit Sub
ErrHandler:
If Not objRs1 Is Nothing Then
If objRs1.State = adStateOpen Then objRs1.Close
Set objRs1 = Nothing
End If
If Not objRs2 Is Nothing Then
If objRs2.State = adStateOpen Then objRs2.Close
Set objRs2 = Nothing
End If
If Err <> 0 Then
MsgBox Err.Source & "-->" & Err.Description, , "Error"
End If
´End
還可以使用當前 Record 的或特定 Field 的 Status 屬性來確定已發生的沖突種類。
至此發生沖突的記錄檢查出來了,那么下一步就是處理失敗的更新。
如何解決錯誤將取決于錯誤的性質和嚴重性以及應用程序的邏輯。實際中的數據庫大都數是多個用戶共享,典型的錯誤是其他人在你修改某個字段之前先修改了該字段。這種類型的錯誤稱為“沖突”。ADO 將檢測這種情況并報告錯誤。
如果存在更新錯誤,將被錯誤處理例程捕獲。通過使用 adFilterConflictingRecords 常量來篩選 Recordset,可以只顯示發生沖突行。
范例:
本錯誤解決方案將打印發生沖突的記錄中的作者的名和姓(au_fname 和 au_lname)。
´Begin:
objRs.Filter = adFilterConflictingRecords
objRs.MoveFirst
Do While Not objRst.EOF
Debug.Print "Conflict: Name = "; objRs!au_fname; " "; objRs!au_lname
objRs.MoveNext
Loop
´END
如果上述情況用事務來處理的話,那么對于用戶數量較大網絡較繁忙且更新記錄數很多的情況來說將嚴重影響速度。上述方法只要處理發生錯誤的記錄,這樣就減輕了網絡傳輸的負荷,有利提高速度。
原文轉自:http://www.anti-gravitydesign.com