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.Op [1] [2] 下一页 [VB.NET程序]ADO 在informix的 Addnew [VB.NET程序]ADO 揭密 1 [Web开发][ADO]如何修改ADO的线程模型 [Web开发]Delphi7下仿Ado.Net类的实现 [Web开发]通过COM使用ADO [Web开发]ADO & ADO.NET中使用存储过程的两个共用的函数 [Web开发]Binding a DataGrid to an ADO Recordset [Web开发]ADOCE for ADO Programmers [Web开发]Delphi多线程下的ADO编程 [Web开发]在delphi.net的VCL.net里使用Ado.net
|