在多进程连接的数据库,并发操作是一个很平常的现象,加上Oracle特有的锁机制(不阻塞读),所以理解与控制并发是一个非常重要的事情。
下面用一个简单的例子说明并发处理中的一个问题,如用户表中存放好评的统计数据,假定两个用户同时操作,看如下一个过程。
one session other session
-----------------------------------------------------------------------------------------
T1> SQL>insert into auction_feedbacks values(?)
1 row inserted
T2> SQL>insert into auction_feedbacks values(?)
1 row inserted
T3> SQL> update bmw_users set rated_sum =
(select count(*) from auction_feedbacks
where username=?)
where id=?;
1 row updated
T4> SQL> update bmw_users set rated_sum =
(select count(*) from auction_feedbacks
where username=?)
where id=?;
1 row updated
T5>SQL> commit;
Commit complete
T6> SQL> commit;
Commit complete
其中时间T1<T2<T3<T5<T5<T6
两个会话,同样的执行语句与同样的执行顺序,都想把增加到好评表中的好评统计放到用户表中去,但是,问题出来了,假定原来该用户的好评是20个,经过两个人的评价后,好评表中最后是22条记录了,而用户表的统计数据是21。
错在哪里?谁都没有错,是并发引发的问题。
要控制好并发,就要深刻理解Oracle的锁机制,Oracle如果一个进程发生数据改变,另外一个进程读该数据的时候,将发生一致性读(以SCN为基准),所以在上面的例子中,进程2读到了进程1 commit之前的统计数,这样就漏掉了会话1发生的好评。
我们要怎么避免并发呢,其实Oracle除了支持一致性读,也支持当前读,也就是说,操作之前检查最新的状态,对于select 可以用序列事务,对于DML本来就是当前读,所以,我们可以利用update的条件中增加需要更新值的原始值来避免并发。
我们利用用户表复制中的防止并发操作来说明,同样的两个会话
会话一
- update rep_users_flag f set f.run_flag='runing'
- where f.run_flag='stop' and f.sp_type='users1';
会话二
- update rep_users_flag f set f.run_flag='runing'
- where f.run_flag='stop' and f.sp_type='users1';
执行同样的语句,如果会话1先执行但是还没有提交的时候,会话2处于等待状态,但是会话1一旦提交,会话2的条件“where f.run_flag=’stop’”的检查将失效(当前读已经是runing,是会话1提交后的数据),所以会话2能更新到的记录数将是0。通过判断sql%rowcount返回的处理行数就可以决定是否继续,如上面的例子,如更新到的行数返回0,将退出或者是等待。
如果在业务频繁的表上面,也可以与实际业务结合起来,如要把表的字段a增加10,可以这样操作
- update table_name set a=a+10
- where id=? and a=10
在以上例子中,假定原表a的值是10,该语句就可以保证只能有一个进程能更新成功。
上一篇: « 怎么改变AIX上使用oracle的一些限制
下一篇: 怎么样在AIX上安装配置Open SSH »
- 发表评论


