我最近需要将一应用程序从SQL-SERVER移植ORACLE,在事务中设置隔离级别时,发现两种类型的数据库虽然命令完全相同,但其工作机制大不一样,下面举例说明:一、有一金额记录变动表,结构为
YZ_HM      char(16)
YZZ_HM    char(3)
SWDM      char(15)
SE          number
BDSX_DM   char(4)
YZ_HM和YZZ_HM为业务号码,SE为变动金额,其中增加额用正数表示,减少额用负数表示,对所有记录求和为余额。二、在应用程序中针对该表的一个业务逻辑为:
begin tran
--设置隔离级别
set transaction isolation level serializable--取得余额,判断是否不足
select sum(se) as se from tn23 where YZ_HM='5002394000059726' and YZZ_HM='001'--减少金额
insert into tn23 (YZ_HM,YZZ_HM,SE,BDSX_DM) values ('5002394000059726',
'001',-50000,'1301')commit tran三、在SQL-SERVER中的并发控制过程如下:1、T1
begin tran
set transaction isolation level serializableselect sum(se) as se from tn23 where YZ_HM='5002394000059726' and YZZ_HM='001'insert into tn23 (YZ_HM,YZZ_HM,SE,BDSX_DM) values ('5002394000059726',
'001',-50000,'1301')
--此时被加上键范围锁,其他事务不能读取、更新、插入和删除YZ_HM='5002394000059726' and YZZ_HM='001'的记录。2、T2
begin tran
set transaction isolation level serializableselect sum(se) as se from tn23 where YZ_HM='5002394000059726' and YZZ_HM='001'
--运行因被T1事务而阻塞,因T1事务使该表加上键范围锁,只能处于等待状态,直到T2结束。3、T1
commit tran4、T2
select sum(se) as se from tn23 where YZ_HM='5002394000059726' and YZZ_HM='001'
--读取T1提交后的余额,如余额不足则返回错误信息,反之则继续操作insert into tn23 (YZ_HM,YZZ_HM,SE,BDSX_DM) values ('5002394000059726',
'001',-50000,'1301')
--减少余额四、在ORACLE中按以上步骤进行测试,得到的结果是不正确的,主要现象如下:T1事务插入减少金额的记录后,在T1没有提交前,T2的运行过程不会受到任何阻碍,也就是说以上第2个步骤不会被T1事务而阻塞,select语句查询出是T1没有提交前的余额,如果T1成功提交,则可能致使T2的余额判断有误,T2也成功提交,出现余额为负的现象。请高手指教该问题应该如何解决,不胜感激!

解决方案 »

  1.   

    难道没有ORACLE高手吗?自己顶一下!
      

  2.   

    在oracle中serializable是针对事务而言,事务中所有语句所看到的数据都是事务开始时刻(执行SET TRANSACTION ISOLATION LEVEL SERIALIZABLE语句时刻)的快照,事务开始之后其他事务提交的更改都看不到,同时执行SET TRANSACTION ISOLATION LEVEL SERIALIZABLE并不锁住记录不让其他事务读写。这是与sql server的区别所在。
    http://epub.itpub.net/3/4.htm
      

  3.   

    bobfang(匆匆过客)兄、各位高手:我仍有两个疑问:
    1、既然SERIALIZABLE隔离级别不会锁住记录,那么两个相同的事务就可能同时插入记录,在事实上产生了幻像读(尽管事务内看起来不会有幻像读)。
    2、怎么解决我在提问中所述的难题?请予帮助!
      

  4.   

    还忘了一点
    3、在事务中随时可能遇到这样的错误:ORA-08177: Cannot serialize access for this transaction,以返回ORACLE系统级错误的方式解决一些并发冲突,感觉无法理解(也许是ORACLE的确太深奥),在编程过程中不好控制(可能随时要判断是否返回这样的结果,总不致于直接将这样的错误提示给用户吧?)。
      

  5.   

    3、"ORA-08177: Cannot serialize access for this transaction"是因为serialize事务操作了其他事务已经提交的行。比如,t1开启了serialize事务,并更新表T的ID=1的行,但未提交,这是事务t2也更新表T的ID=1的行并提交,t2可以commit,此后t1执行commit,就会报ORA-08177错误。
    2、要防止其他事务也处理相同的行,有几种解决方法:
    a、在更新时也要检查更新前的条件是否没有变,如果变的不更新。例如更新前查到记录id=1的v=100,然后要更新这条记录的v,可以这样做
    update t set v=NEWVALUE where id=1 and v=100;
    if SQL%rowcount<>1 then
      --说明记录已被其他会话更新,异常
      ...
    end if;
    b、可以在select时加for update子句,锁住记录,如你上面的例子可以在select sum() ...之前加上一句
    select 1 into V from tn23 where YZ_HM='5002394000059726' and YZZ_HM='001' and rownum=1 for update;
      

  6.   

    匆匆过客兄,非常感谢你对我的帮助,想继续请教你:
    1、你提出的解决办法b虽然比较有可行性,但每次做起来比较麻烦。
    2、SERIALIZABLE隔离级别得出的结果好象与SQL-92标准不符(我倒觉得SQL-SERVER出现的结果容易理解和运用),请问它究竟该如何使用?最好举例说明。
      

  7.   

    我也来说几句,不对的请谅解:
    可能是因为oracle有回滚段undo segment和undo tablespace.保存的都是数据的before image.
    oracle有个概念叫一致性读(consistent get),一般的select都是一致性读, 它会去回滚段读出before image,而不会看到脏数据.
    我记得SQL SERVER好象只有日志和数据文件,对应oracle的redo,data file.但就缺少undo.所以
    SERIALIZABLE隔离级别会锁住记录,而oracle不会.
    正是由于undo并且可以设置undo retention的大小,从9i开始提供了闪回的功能,至少SQL SERVER现在还没看到此功能.
      

  8.   

    各位仁兄:愚弟虽然具有一定的编程经验,但才学ORACLE,也许是受SQL-SERVER影响过深,对ORACLE有些东西尚不能理解:
        1、大家都说ORACLE提供回滚段的机制非常优秀,我的理解是他提高了事务之间的并发性,对前台响应速度更快,但SERIALIZABLE隔离级别得出的结果好象与SQL-92标准不符(我倒觉得SQL-SERVER出现的结果容易理解和运用),请问它在程序中究竟该如何使用?对ORA08177错误如何处理?最好举例说明。
        2、如果照匆匆过客兄的说法,为了防止幻像读而先在Select语句后跟上for update关键字,锁住所读取的记录,那么其并发性与SQL-SERVER不是一样的了吗?并且还要多写一条加锁语句(SQL-SERVER中不用),那ORACLE的优越性到底又体现在什么地方呢?    敬请各位高手解惑,最好提供QQ号直接进行交流,不胜感激!
      

  9.   

    我的QQ号是249335006,希望能与ORACLE高手成为朋友(在加为好友的信息中请注明"讨论ORACLE"字样),谢谢!
      

  10.   

    关于事务之间的并发性,举个这样需求——
        在一个十分繁忙的系统中对业务数据统计,需要统计的表有多张,表中的数据会随时被更新,统计的过程无法通过1个SQL完成,需要用存储过程,统计执行时间要几分钟。
        如果用oracle来实现,可以设置这个存储过程使用一个read型事务或者是serialize型事务,能够确保各个sql统计的数据都是事务开始时分的记录,不会出现多条语句统计的数据的不一致性,同时也不影响其他事务对这些表的读写。
        如果换成sql server,请问如何实现?