top
Loading...
ADO的測試
DO Cursor/Lock/Concurrency 的測試 來源:cww
我個人認為ADO2.0在這方面的表現實在是不好,我看ADO更高的版本會不會比較好一點。或許,要在SQL7.0之下才會有良好的表現,而我使用的是SQL 6.5與Informix。怎堋說呢?
注:我終於用有SQL7.0可以Testing了,而且Concurrency的表現不錯哦,所以了,如果
您想用ADO而且是SQL Server當後端,那就使用Sql7,不要用SQL6.5

我們先看一下Recordset中CursorType的屬性

AdOpenForwardOnly 順向資料指標?
AdOpenKeyset 索引鍵集 (Keyset) 資料指標?
AdOpenDynamic 動態資料指標?可看到其他使用者所做的增加,變更和刪除結果
AdOpenStatic 靜態資料指標

以上這一些和RDO的定義沒有什堋不同, 然而我也在該篇文章中指出,這些要和Cursor所在的位置與RDBMS都有相關。

RDO中Cursor的位置可分為rdUseODBC(對應ADO的AdUseClient),rdUseServer(對應ADO的AdUseServer),rdUseNone(ADO中沒有直接的對應)。ADO Recordset的CursorLocation = AdUseClient時,只有AdOpenStatic/AdOpenForwardOnly的CursorType會有作用,其他的二者和AdOpenStatic有相同的效果。如果是AdUseServer呢,在SQL Server中上述的四種CursorType都可以用,但是RecordSet中的Resync方法只有
在adOpenKeyset的CursorType才能用,AdOpenStatic不能使用。而RDO中經常使用的rdUseNone的CursorType在ADO中沒有相對應的設定,那該如何呢?
那得設定
rs.CursorLocation = AdUseServer
rs.CursorType = AdOpenForwardOnly
rs.LockType = AdLockReadOnly
rs.CacheSize = 1
來取代RDO的rdUseNone,而且注意的是不可使用Client端的Cursor,否則RecordSet Open的時間會久(因為Client端的Cursor在ADO中都是Static的Cursor而不管我們如何設)。

OpenLink ODBC Driver for Informix又有點不一樣,如果使用Server端的Cursor,那只能設定是AdOpenForwardOnly的CursorType,否則會有意想不到的結果,而使用Client端的Cursor呢,只有AdOpenStatic/AdOpenForwardOnly二者有效,不過特別的是AdOpenStatic
在這里呢卻可以使用Resync的方法,只要說我們的Table有Unique的Key且有Select進來,便可以用,因為下rs.Resync adAffectCurrent 在Informix 中是以另一個查詢當筆資料的Query來做(如Select * from TableName Where KeyFl eyvalue),就因為要Query到當筆,所以一定要有Unique的Key,否則Resync會有錯。

再來討論一下Lock
RecordSet的LockType有以下的設定:

adLockReadOnly 預設值。唯讀 -- 您無法變更資料。
adLockPessimistic 悲觀性鎖定,通常會在編輯時立即在資料來源鎖定記錄。
adLockOptimistic 樂觀性鎖定,在您呼叫 Update 方法時才鎖定。
adLockBatchOptimistic 悲觀性批次更新

悲觀性批次更新我不討論,而唯讀也夠明顯了,這都不討論。基本上SQL6.5用OLEDB Provider來連時,只有樂觀鎖定,SQL7.0後才有悲觀鎖定。SQL6.5想要有悲觀鎖定,那就用ODBC Provider的方式來做吧!RDO悲觀鎖定基本上在Resultset建立時,便會自動Fetch第一筆,所以在該筆所在的Page上會有一個Update Lock。而ADO又不太相同,它在Recordset pen之後并不會自動Fetch第一筆,直到我們想引用它或Move系列指令下達時才會有作用。


上面的Lock是針對Recordset,也就是以Page/Record為對像,如果想要做Table Exclusive Lock於SQL6.5 設定SQL指令如下:

Select * from qppfa (TabLockX) Where ....

TabLockX代表會對該Table做Exclusive Lock,不過這種Lock對程式設計沒有太多用處,因為這種方式的Lock會在Fetch時做Table Lock,一旦Update資料或Transactiont結束後會令該Lock Release掉,如果沒有Transaction,那情況是:Fetch時做Table Lock,Update
後Release Lock,Fetch下一筆時再產生Table Lock,而不是們想像的會一直做Lock。

而Informix的Table Lock則好多,它可以用Lock Table table-name in Exclusive Mode的指令來做,也就是說我們用Connection物件的Execute方法來執行上述的指令便可。

在SQL Server6.5之下

1.不像RDO2.0 有一個rdConcurLock的設定;雖然ADO有一個adLockPessimistic的LockType,如果透過OLEDB Provider來做,似乎全不是那一回事,怎堋用,都是樂觀的鎖定。除非使用ODBC Privder的方式,才會有悲觀鎖定。

2.RDO 中如果ProgramA 與 ProgramB 同時指到某一筆Record,而後ProgramA成功的Update該筆資料且Commit,而ProgramB這時也隨即Update該筆資料,這時ProgramB會收到一個錯誤訊息,這時只要下

Recordset.Move 0
Recordset.Edit
設定更改的值
Recordset.Update 便可以重新來做一次

但是ADO呢,我個人認為在Update之後產生錯誤時,正確的使用方式應是:
(rs as ADODB.Recordset)
rs.CancelUpdate
rs.Resync adAffectCurrent
set new value for recordset
rs.Update
但實№上會在rs.Resync adAffectCurrent這一行再產生錯誤,而此時Recordset的內容卻有Refresh成Remote資料庫的實№資料內容,好奇怪!我不知道這里是我的做法有誤,還是SQL Server 7.0才能如此,至少SQL Server6.5我失敗了。以至於Update的程式要變成:

cn.BeginTrans
On Error Resume Next
rs!fld1 = "v"
rs.Update
Do While cn.Errors.Count $#@62; 0
If cn.Errors(cn.Errors.Count - 1).Number = -2147217887 Then
-2147217887 代表該Record可能被他人更新了 for Server端Cirsor
ans = MsgBox("資料更新有沖突,是否再試一次", vbYesNo)
If ans = vbYes Then
rs.CancelUpdate
rs.Resync adAffectCurrent 這里也會產生一個error
cn.Errors.Clear
rs!fld1 = "v"
rs.Update
Else
Exit Do
End If
Else
Exit Do
End If
Loop

If cn.Errors.Count = 0 Then
cn.CommitTrans
Else
cn.RollbackTrans
End If

以上程式是rs.CursorLocation = adUseServer 的情況,而用rs.CursorLocation = adUseClient呢,情況又不太相同,基本上這是不提供rs.Resync方法。這種程式能看嗎?實在不好吧?所以在這里我的建議是,使用OLEDB Provider來連SQL6.5時,最好是同一筆資料同時間被Update的機率很小,如果發生了,就只好Requery,再想辦法指到該筆,再重新Update,要不就要用上面的方式,再不就產生錯誤,要使用者再重新執行一次。只要同時Update的可能性小,可能好幾年都不會遇上同時Update的問題。

我們知道在實№的應用上有不少是有可能同時Update同一資料的,例如說,我們用一筆Record來記錄流水號,每個Process要取得該Record流水號的那個欄位之內容,之後加1再存回去,這種情況在多人使用時就很有可能有同時Update的情況,那我建議使用ODBC
Provider的悲觀鎖定,不過,仍有些問題,如果以下程式所示,而有兩個Process A,BA執行到rs.MoveFirst後換 B執行到rs.MoveFirst,此時有Dead Lock產生,所以B 會進入等待,而A 呢,它執行到Update時會產生錯誤,天!這種程式能用嗎?所幸,SQL7不會有回題。

Private cn As ADODB.Connection
Private rs As ADODB.Recordset


Dim connstr As String
Dim ans As Integer, errstr As String, sql As String
Set cn = New ADODB.Connection
connstr = "Driver={SQL Server};UID=cww;PWD=jjh5612;Server=OPEN_VIEW;Database=cwwtest"
cn.Provider = "MSDASQL"
cn.ConnectionString = connstr

cn.Open
cn.BeginTrans
sql = "Select * from qppfa where case_no = E8701761 and seq BETWEEN 1 AND 5 "
Set rs = New ADODB.Recordset
Set rs.ActiveConnection = cn
rs.CursorLocation = adUseServer
rs.Source = sql
rs.Open , cn, adOpenKeyset, adLockPessimistic, adCmdText
rs.MoveFirst 這時候才真的有Update Lock
rs!kind = "v"
rs.Update
cn.CommitTrans
$#@60;/pre$#@62;$#@60;/td$#@62;$#@60;/tr$#@62;$#@60;/table$#@62;在Informix之下(用OpenLink的ODBC Driver)呢,情況很亂,後來我發現只要使用Client
端的Cursor,問題會減到最少,而且這里有一點十分奇特,OpenLink ODBC Driver允許
我們使用Recordset的Resync方法,不但沒有錯,而且還會把on-line Database的資料
傳回來(這一點和使用RDO 的Client端Cursor不同,和 ADO SQL Server6.5也不同)。
不過這里要特別提出的是Resync adAffectCurrent 只在我們的Table有Unique的Key,
而且這個Key有在我們的Select的范圍內才有效。所以,以下是我OpenLink ODBC Driver
解決Update concurrency的方式。特別提出是,在這OpenLink ODBC Driver下使用ADO,
千萬不要用Server端的Cursor,會有太多問題(除非是AdOpenForwardOnly)。以下的程式
只可用於OpenLink Informix ODBC Driver,不可用於SQL Server!!

$#@60;table border$#@62;$#@60;td$#@62;$#@60;tr$#@62;$#@60;pre$#@62;

Dim WithEvents cn As ADODB.Connection
Private WithEvents rs As ADODB.Recordset
Private qry As ADODB.Command
Private adoerr As ADODB.Errors

以下是 update資料庫的部份
Private Sub UpdateData()
Dim ans As Integer
cn.Execute "Set lock mode to wait 15" 該設定只對Informix有效
cn.BeginTrans
rs.MoveNext
On Error GoTo errh 設定Update 的錯誤處理函式
updArea: 設定這一個標記,方便錯誤函式返回這里做事情

rs!fld1 = "h" 自行更改成update所的設定
rs.Update 如果Update時產生錯誤時,則會到errh處處理
cn.CommitTrans
Exit Sub

errh:
Do While cn.Errors.Count $#@62; 0
If cn.Errors(cn.Errors.Count - 1).Number = -2147217864 Then
-2147217864 代表該Record可能被他人更新了 for Client端Cirsor
ans = MsgBox("資料更新有沖突,是否再試一次", vbYesNo)
If ans = vbYes Then
rs.CancelUpdate
rs.Resync adAffectCurrent 重新到Database讀取當筆資料
cn.Errors.Clear
Resume updArea 重回資料更新的區域
Else
Exit Do
End If
Else
Exit Do
End If
Loop
MsgBox "Update失敗" + vbCrLf + cn.Errors(0).Description, vbCritical
cn.RollbackTrans
End If
End Sub

開啟資料庫
Private Sub Form_Load()
Dim connstr As String
Dim ans As Integer, errstr As String, sql As String
Set cn = New ADODB.Connection
connstr = "UID=cww;PWD=jjh5612;Database=cwwpf@eis;" _
+ "Driver={OpenLink Generic 32 Bit Driver};" _
+ "Host=192.168.0.61;" _
+ ";FetchBufferSize=30" _
+ ";NoLoginBox=Yes" _
+ ";Options=" _
+ ";Protocol=TCP/IP" _
+ ";ReadOnly=No" _
+ ";ServerOptions=" _
+ ";ServerType=Informix 7.2"
cn.ConnectionString = connstr
cn.Open
sql = "Select * from testtab order by case_no"
Set rs = New ADODB.Recordset
Set rs.ActiveConnection = cn
rs.CursorLocation = adUseClient
rs.Open sql, cn, adOpenKeyset, adLockOptimistic
End Sub


SQL7.0呢,這就很好用了,不管樂觀鎖定或悲觀鎖定都有很好的表現,我們先看悲觀鎖定:
Private Sub Form_Load()
Dim connstr As String
Dim sql As String
Set cn = New ADODB.Connection

connstr = "Data Source=ACCOUNT;UID=sa;PWD=;Initial Catalog=NKIUAcc"
cn.Provider = "SQLOLEDB"
cn.ConnectionString = connstr
cn.Open
cn.BeginTrans
sql = "Select * from TESTTAB"
Set rs = New ADODB.Recordset
Set rs.ActiveConnection = cn
rs.CursorLocation = adUseServer
rs.Source = sql
rs.Open , cn, adOpenKeyset, adLockPessimistic, adCmdText
rs.MoveFirst
rs!f1 = "x"
rs.Update
cn.CommitTrans

這樣做,如果同時有兩個Process執行完rs.Open,而接著都會執行rs.MoveFirst,這時只有一個Process會成功,另一個則會進入等待,等到先前的Process Release Lock後才會再執行,這樣子就解決了同時Update一筆資料的問題。唯一要注意的只有等待的時間若太久則會TimeOut,所以改成以下的方式:

Dim cn As ADODB.Connection
Private rs As ADODB.Recordset
Private Sub Form_Load()
Dim connstr As String
Dim sql As String
Set cn = New ADODB.Connection

connstr = "Data Source=ACCOUNT;UID=sa;PWD=;Initial Catalog=NKIUAcc"
cn.Provider = "SQLOLEDB"
cn.ConnectionString = connstr
cn.Open
sql = "Select * from TESTTAB"
Set rs = adoOpenRecordset(cn, sql, atServer, 悲觀)
cn.BeginTrans
If rs Is Nothing Then
cn.RollbackTrans
Else
rs!f1 = "q"
rs.Update
cn.CommitTrans
End If

以下在.Bas
Public Enum adCursorLoc
atClient = 0
atServer
End Enum
Public Enum adLockType
唯讀且向前 = 0
悲觀
樂觀
唯讀
End Enum
Public Function adoOpenRecordset(Conn As adodb.Connection, Source, _
Optional CursorLoc As adCursorLoc, Optional LockType As adLockType) As adodb.Recordset
Dim rs As adodb.Recordset
Dim tryTimes As Integer
Dim vv As Variant
Set rs = New adodb.Recordset
If LockType = 唯讀且向前 Or LockType = 悲觀 Then
CursorLoc = atServer
rs.CacheSize = 1
End If
If CursorLoc = atClient Then
LockType = 樂觀
End If
If CursorLoc = atServer Then
rs.CursorLocation = adUseServer
Select Case LockType
Case 唯讀, 唯讀且向前
rs.LockType = adLockReadOnly
Case 悲觀
rs.LockType = adLockPessimistic
Case 樂觀
rs.LockType = adLockOptimistic
End Select
Else
rs.CursorLocation = adUseClient
rs.LockType = adLockOptimistic
End If
Err.Clear
On Error GoTo errh
If TypeOf Source Is adodb.Command Then
rs.Open Source
Else
rs.Open Source, Conn
End If
vv = rs.Fields(0).Value
Set adoOpenRecordset = rs
Exit Function
errh:
If Err.Number = -2147467259 Then
Time Out Lock by Others
If tryTimes $#@60; 2 Then
tryTimes = tryTimes + 1
Err.Clear
Resume
Else
Set adoOpenRecordset = Nothing
End If
Else
Set adoOpenRecordset = Nothing
End If
End Function


如果是樂觀鎖定呢,如果有Concurrency的錯誤時,它會在Update時才有錯,而我們使用的方式是先CancelUpdate後再使用Resync AffectCurrent來Refresh Current Record,而後再把Update的程序再重新執行一次,如下:但要注意的是,要使用Client端的Cursor
要不然,Server端的Cursor是不支援這個Resync Method的

Option Explicit
Private cn As ADODB.Connection
Private rs As ADODB.Recordset

Private Sub Form_Load()
Dim connstr As String
Dim ans As Integer, errstr As String, sql As String
Set cn = New ADODB.Connection
connstr = "Data Source=ACCOUNT;UID=sa;PWD=;Initial Catalog=NKIUAcc"
cn.Provider = "SQLOLEDB"
cn.ConnectionString = connstr
cn.Open
cn.CommandTimeout = 5

sql = "Select * from TESTTAB"
Set rs = adoOpenRecordset(cn, sql, atClient, 樂觀) 要用Client端Cursor
cn.BeginTrans
If rs Is Nothing Then
cn.RollbackTrans
Else
Call UpdateRtn
End If
If Err.Number = 0 Then
cn.CommitTrans
Else
cn.RollbackTrans
End If

End Sub

Private Sub UpdateRtn()
Dim ntx As Integer
On Error Resume Next
Do While True
rs!f1 = "q"
rs.Update
If Err.Number = 0 Then
Exit Do
Else
If Err.Number = -2147217864 Then 發生Concurrency錯誤
If ntx $#@60; 2 Then
ntx = ntx + 1
Err.Clear
rs.CancelUpdate
rs.Resync adAffectCurrent 重新Refresh Current Record
Else
Exit Do
End If
Else
Exit Do
End If
End If
Loop
On Error GoTo 0
End Sub

作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗