分类:
2009-10-11 13:46:29
来源:cww 前几个Topic提到Cursor是在Client/Server端建立的差别,再来是Cursor是ForwardOnly 或Keyset/Static/Dynamic的型式,再来是读取到Data后是悲观锁定与乐观锁定。现在则 要谈Concurrency的另一个重点,Isolaction Level。 ANSI 的标准中,Transaction的Isolaction Level主要可分成以下三种 1.Read Uncommitted (Dirty Read) 不管该Record有没有被Lock住,先读了再说,就算该资料后来被RollBack也不管。 2.Read Committed 保证读到资料已经是Committed的资料,但是如果是Scroll Cursor,这次读和下 次读取同一笔资料时,不保证资料会相同 3.Repeatable Read/Serializable 保证同一个Transaction中Scroll Cursor每一次读取到的资料内容都相同,这是 因为读取到资料之后,便在该Record或Record所在的Page上加一个Share Lock, 因此别人只能读,但是不能修改。注意,这个等级的Isolation Level也是保证 Read Committed。 而一般来说,SQL Server的内定是Read Committed,Informix Dynamic Server中如果该 Database有Log亦是Read Committed,否则是Dirty Read。 这个东西对我们RDO有何响呢?有! 看一下以下的例子 Process A 和Process B同时执行以下的程序片断
Set en = rdoEnvironments(0) Set cn = New rdoConnection cn.CursorDriver = rdUseServer connstr = "DSN=SQLSRV;UID=cww;PWD=jjh5612;" cn.Connect = connstr cn.EstablishConnection rdDriverNoPrompt, False sql = "Select * From testtab" Set qry = cn.CreateQuery("MyQuery", sql) qry.SQL = "Select * from testtab" Err.Clear rdoErrors.Clear On Error GoTo Errhandle 1. cn.BeginTrans 2. qry.RowsetSize = 1 3. Set rs = qry.OpenResultset(rdOpenKeyset, _ rdConcurValues) 4. rs.MoveLast 5. rs.Edit 6. rs.rdoColumns(1) = "o" 7. rs.Update 8. If rdoErrors.Count <> 0 Then 9. cn.RollbackTrans 10. Else 11. cn.CommitTrans 12. End If Errhandle : |
情况一: 如果Process A已执行到行号8 的if rdoErrors.Count <> 0 then,而Process B方要执 行号3的OpenResultset,结果会如何呢?因为Process A在最后一笔加以Update,而且 Transaction尚未结束,而rdOpenKeyset的方式会把Table从头扫到尾,扫到最后一笔时 发现该笔Data尚未Committed(有个Exclusive Lock於其上),又加上内定的Read Committed ,结果变成是Process B执行到OpenResultSet会停住(我没有用非同步开启),它在等那 一笔被Exclusive Lock的资料解锁,等到TimeOut时,便会产生Error SQL Server Error rdoErrors(0).Number:40087 执行动作被取消, rdoErrors(1).Number:0 S1008: [Microsoft][ODBC SQL Server Driver] Operation canceled Err.Number = 40087 Informix Error rdoErrors(0).Number:0 37000: [OpenLink][ODBC][Driver]Syntax error or access) Err.Number = 40002 情况二: 如果Process A执行到行号6,而Process B执行完行号3,Process A执行完行号7之Update 指令(此时Transaction未结束)而Process B执行MoveLast时会进入等待,Timeout时会有 Error,这是因为Read Committed之故,而情况一多了一个Share Lock的问题,而这里因 为乐观锁定在读出资料后就没有Lock,所以没有Share Lock的问题。 SQL Server Error rdoErrors(0).Number:0 S1T00: [Microsoft][ODBC SQL Server Driver] Timeout expired Err.Number = 40002 Informix Error rdoErrors(0).Number -243 S1000: [OpenLink][ODBC][Informix Server] Could not position within a table (cww.testtab). (-243 Err.Number = 40002 情况三: 如果Process A执行到行号6,而Process B执行到号6,Process A执行完行号7之Update 指令(此时Transaction未结束)而Process B执行Updtae时会进入等待,等到TimeOut时 产生另一种错误讯息,但这里要注意,Informix没有产生err的错误,只是rdoErrors 有东西,也就是说,这类的错误不能用On Error的方式来拦截。 SQL SERVER Error rdoErrors(0).number 16934 01S03: [Microsoft][ODBC SQL Server Driver] [SQL Server]Optimistic concurrency check failed, the row was modified outside of this cursor Err.Number = 40041 Informix Warnning rdoErrors(0).Number 0 37000: [OpenLink][ODBC][Driver]Syntax error or access rdoErrors(1).Number -243 S1000: [OpenLink][ODBC][Informix Server] Could not position within a table (cww.testtab). (-243 Err.Number = 0 情况四: 如果Process A执行到行号8,也就是说在最后一个Row上有一个Exclusive Lock,而 Process B 执行行号3,但是改成 Set rs = qry.OpenResultset(rdOpenForwardOnly, rdConcurValues) 那麽到底有没有可能有错?答案是不一定,如果这rdOpenForwardOnly的Resultset不用 Scan table,或者不用读到被Lock的那一笔Data,便可Open 成功者,那这OpenResultSet 不会有任何等待而成功。如果说,就算被Lock住的那一笔不在目前Process的Resultset之 中,但因为要Scan过它才知有或没有在Resultset中,那麽,这OpenResultset的动作也会 进入等待。如果是Page Lock的情况,那影响的Row就更多了。 看完上面的数种情况,不难知道Currency的Handle真的好麻烦,重点在於Transaction 的时间不可过久,以免被Lock住而产生Error。 上面所提的都是Read Committed的情况,而Dirty Read / Repeatable Read呢?我们来 如何设定这二者?首先建立起Connection,再来使用Transaction,最后设定之,如下 Set en = rdoEnvironments(0) Set cn = New rdoConnection cn.CursorDriver = rdUseServer connstr = "DSN=SQLSRV;UID=cww;PWD=jjh5612;" cn.Connect = connstr cn.EstablishConnection rdDriverNoPrompt, False sql = "Select * From testtab" Set qry = cn.CreateQuery("MyQuery", sql) qry.SQL = "Select * from testtab" Err.Clear rdoErrors.Clear On Error GoTo Errhandle cn.BeginTrans '-----------就在这里了 ---------------------------------------------------- ' cn.Execute "Set TRANSACTION ISOLATION LEVEL Repeatable READ", rdExecDirect ' cn.Execute "Set TRANSACTION ISOLATION LEVEL READ UNCOMMITTED", rdExecDirect ' cn.Execute "Set TRANSACTION ISOLATION LEVEL READ COMMITTED", rdExecDirect '-------------------------------------------------------------------------- Set rs = qry.OpenResultset(rdOpenKeyset, _ rdConcurValues) 可以把想要的方式UNMARK,如果说使用的是Read Uncommitted的方式,那是不管他人有没 有在Record上做Lock,先读了再说,所以情况一、情况二都不会有Error产生,只不如果 以Read Uncommitted的方式开启的Cursor,它只能是ReadOnly的Cursor,它是不能用来修 改的。 如果说使用的是Repeatable Read的方式,它和悲观锁定有一点像,又不太像,Repeatable Read的方式在开启Cursor时,会在它曾读取过的资料上做Share Lock,也就是说,如果所 要的资料需Scan table(没有用到Index),那麽,不管该资料有没有在Resultset之中,只 要读取过,便在上面做一个Share Lock(而悲观锁定则是Update Lock),如果有用上Index 的Cursor状况就比较好,不在Resultset的Row不会有Share Lock(如果是Page Lock那就未 必了)。为什麽Repeatable Read这麽"龟毛",人家不在Resultset中的也想要在其上做一 个Share Lock?其实有它的道理,Repeatable Read保证在Cursor Open期间读过的资料会 固定,不允许他人突然更新/新增资料,而使该Data会落在Resultset之中。我还是觉得这 样做不是方便啦。Repeatable Read做的是Share Lock 因此,可以有两个Cursor同时以 Repeatable Read的方式来Open相同的Resultset(和悲观锁定不同)。就是因为Repeatable Read有这麽多的限制,它大量降低Concurrency,所以实作上很少这样子做。 Informix则在Set Transaction Isolation Level之外多新增了 Set Isolation Set Transaction 相对於 Set Isolation ----------------- ------------------- Read Uncommitted Dirty Read Read Committed Committed Read 没提供 Cursor Stability Repeatable Read Repeatable Read 设定的方式是 cn.Execute "SET ISOLATION TO CURSOR STABILITY", rdExecDirect cn.Execute("SET ISOLATION TO DIRTY READ", rdExecDirect cn.Execute("SET ISOLATION TO REPEATABLE READ", rdExecDirect cn.Execute("SET ISOLATION TO COMMITTED READ", rdExecDirect 直接把以上的方式取代 cn.Execute "Set TRANSACTION ISOLATION LEVEL ... 的位置便可以。 Informix的 Cursor Stability是介於Read Committed与Repeatable Read的一个方式, Cursor Stability是在Current Record上有一个Share Lock,当读取到其他笔时便Release 现有的Lock,而在新的资料上做Share Lock(基本上它也必需有Read Committed的功能)。 当然了,如果您将Current Record Update了,那会在该笔之上有一个Exclusive Lock ,一直到Transaction结束,这和Committed read / Repeatable Read没有不同。这里要 注意,不管是Repeatable Read/ Cursor Stability的Cursor,在Transaction结束时,会 把所有的Lock Release;在SQL SERVER呢,Transaction结束时也就没有Current Recotrd 了,但Informix则不同,Transaction结束了,所有Lock 了,但是仍有Current Record ,那应该是OpenLink的Informix ODBC Driver对於Server端的Cursor是以Hold Cursor 的方式来建立之故(Hold Cursor指的是在Transaction结束时,仍保有Currenmt Record)。 即然知道设定Cursor Stability后的Cursor是在Current Record上做Share Lock,那麽 其他的例子就不再多做说明,可以自行研究。 不过我们查Informix的手册,人家会告诉我们,如果Cursor可用来Update,那Repeatable Read/Cursor Stability会在目标Object上放Update Lock而不是Share Lock,但使用ODBC 来做时,其结果是我上面所说的Share Lock,这中间的原因我不是很清楚。 上面的例子中说到Informix 读取资料时也会等待Lock的的Release,不过内定是没有这 样子做,但我们可以用以下的方式来改 cn.Execute "Set Lock Mode to Wait" 设定遇Lock则无限等待 cn.Execute "Set Lock Mode to Not Wait" 遇Lock则不等待,立即产生Error cn.Execute "Set Lock Mode to Wait 10" 等待10秒,若未能读取成功才产生Err 它放的位置没有太多的限制,只要是Connection建立起来后便可,而且执行后立即生效。 一般我会放在OpenResultset之前,因为OpenResultSet是第一个会读取资料的方法。透 过OpenResultset的Server端Cursor来做Insert/Update/Delete/Move时,正常情况以下 每一个步骤都得有一段Error Check的动作以防concurrency所造成的问题。 OpenResultset Move 系列 Update/AddNew/Delete Cursor有Lock,便会降低Concurrency,这不是一件好事,而且Lock会花费系统的资源,而 且我们也很难去知道DBMS到底Lock住了多少Row,而且不能保证重新执行时Lock的状态 会和上次相同。因此能不Lock就不要用Cursor Stability来做(更不用说Repeatable Read) ,另外,能不用Scroll Cursor做就不要用之,因为它花费Resource而且也会降低Concurrency ;如果是一个查询用的Cursor,大可以用rdUseNone的方式来开Cursor,那才是正途。 最后,如果不得以,一定得用Scroll Cursor,能Readonly那也会比较好,如果ReadOnly 也不成,那要想办法降低Resultset内的笔数,但也顾及Index的使用(否则table Scan就 更惨)。 见了以上及前几篇文章的说明,您相信透过ODBC连接后端数据库时,若想改变后端的数据库, 会只是单纯的改Connection字串而以吗?我想不见得吧,说不定还得花很多的时间来testing 新的ODBC Driver之特性与后端RDBMS的功能呢!因此最好的方式是使用最单纯的做法来做 ,最单纯的部份理论上各个后端与各个ODBC Driver都相同的。