我们已经知道oracle多版本机制能够提供一致性的答案,而且还有高度的并发性。但是真的就没有其他不好的一面吗?
案例1:
假设有一个资源调度程序(可以用来调度会议室,投影仪等资源),业务规则是:在给定的时间段不能将一种资源分配给多人。这里涉及到资源表(Resources)与资源调度表(Secludes)。
我们一般的实现方式是,先查询某一时间段的某一资源是否已经被分配,如果没有被分配,我们就可以使用。
查看是否被分配SQL语句:
select count(*) from schedules where resource_name = :room_name
and (start_time <= :new_end_time) and (end_time >= :new_start_time)。 ---SQL1
然后插入分配记录(如果得到结果为0)
insert into schedules ( resource_name, start_time, end_time )
values ( p_resource_name, p_start_time, p_end_time ); ---SQL2
但是如果我们让2个人同时预定13:00到14:00期间的投影仪资源,可能都会预定成功。假设一个人是事务A,另外一个人是事务B,在事务A执行SQL1语句的时候,事务B也开始执行SQL1语句(因为oracle里面查询是不会被阻塞的)。那么事务A与事务B查询的结果都是0,于是都可以申请到投影仪资源了。
解决方案:
应该在查询之前先锁定要申请的资源,使得事务A与事务B在执行SQL1与SQL2语句的时候串行。
在执行SQL1语句之前先执行如下SQL语句:
select * from resources where resource_name = :room_name FOR UPDATE; ---SQL3
这样当事务A执行完SQL3之后,事务B执行SQL3的时候就会被阻塞。除非事务B申请资源跟事务A不同。
案例2:热表上超出期望的I/O
在生产环境中在一个大负载条件下,一个查询使用的 I/O 比你在测试或开发系统时观察到的 I/O 要多得多。造成这种现象的原因是:在你测试系统中,由于它是独立的,所以不必撤销事务修改。不过,在生产系统中,读一个给定的块时,可能必须撤销(回滚)多个事务所做的修改,而且每个回滚都可能涉及I/O来获取undo信息并应用于系统。
因为oracle提供多版本机制的原理是,读取数据的时候判断这个数据是不是已经被修改了,如果被修改了就从undo段去获取修改前的数据。如果发现这个修改前的数据还不是想要的数据,继续找更老的数据,直到找到开始执行SQL语句那一刻这个数据的值。
所以在oracle里面如果一个表经常被修改,查询,会发生这样的情况。因为查询的时候,如果被修改的数据越多,需要读取undo段的信息就越长,I/O操作自然也会越多。对于这种热表的查询,最好能够把查询的时间压缩到最短,以便在查询期间,这个数据被其他事务修改的概率最低,从而需要从undo段读取的数据最少,最大限度减少I/O操作的概率。