并发容易出现的问题与并发控制
在多进程连接的数据库,并发操作是一个很平常的现象,加上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 两个会话,同样的执行语句与同样的执行顺序,都想把增加到好评表中的好评统计放到用户表中去,但是,问题出来了,假定原来该用户的好评是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,将退出或者是等待。
如果是更新频繁的业务表,需要与业务表相关实现该功能,如
代码:
update tabele set a=a+10 where id=? and a=10; ---假定a原来的值是10
.