考虑这么一个场景:等待处理的任务加入数据库某个表中,表的PROCESSED_FLAG列只有两个值:Y 和N。对于插入到表中的记录,该列值为N(表示未处理)。其他进程读取和处理这个记录时,就会把该列值从N更新为Y。
位图索引适用于低基数(low-cardinality)列,所谓低基数列就是指这个列只有很少的可取值。但是这里并不适合。采用位图索引,一个键指向多行,可能数以百计甚至更多。如果更新一个位图索引键,那么这个键指向的数百条记录会与你实际更新的那一行一同被有效地锁定。
可以在函数decode(process_flag, 'N', 'N')上创建一个基于函数的B*Tree索引,从而返回N或者NULL,利用完全为NULL的键不会放入B*Tree索引的特性,只在处理标志为N的记录上创建一个索引。
为了能够多个会话并行处理任务,在Oracle11g R1之后,可以利用SKIP LOCKED特性。它允许多个会话并发查找第一个未锁定,未处理的记录,然后锁定该记录进行处理。
下面举例说明。
创建表格,索引,添加数据:
create table t ( id number primary key, processed_flag varchar2(1), payload varchar2(20) ); create index t_idx on t( decode( processed_flag, 'N', 'N' ) ); insert into t select r, case when mod(r,2) = 0 then 'N' else 'Y' end, 'payload ' || r from (select level r from dual connect by level <= 5);
现在表格T中的数据如下:
tony@ORA11GR2> select * from t;
ID PR PAYLOAD
---------- -- ----------------------------------------
1 Y payload 1
2 N payload 2
3 Y payload 3
4 N payload 4
5 Y payload 5
在2个会话中分别执行如下的过程块,
declare l_rec t%rowtype; cursor c is select * from t where decode(processed_flag,'N','N') = 'N' FOR UPDATE SKIP LOCKED; begin open c; fetch c into l_rec; if ( c%found ) then dbms_output.put_line( 'I got row ' || l_rec.id || ', ' || l_rec.payload ); end if; close c; end; /
在第一个会话中,得到结果: I got row 2, payload 2
在第二个会话中,得到结果: I got row 4, payload 4
在Oracle11g R1之前,可以利用如下办法:
create or replace function get_first_unlocked_row return t%rowtype as resource_busy exception; pragma exception_init( resource_busy, -54 ); l_rec t%rowtype; begin for x in ( select rowid rid from t where decode(processed_flag,'N','N') = 'N') loop begin select * into l_rec from t where rowid = x.rid and processed_flag='N' for update nowait; return l_rec; exception when resource_busy then null; end; end loop; return null; end; /
在2个会话中分别执行如下过程,也可以得到相同结果:
declare l_rec t%rowtype; begin l_rec := get_first_unlocked_row; dbms_output.put_line( 'I got row ' || l_rec.id || ', ' || l_rec.payload ); end; /
*如果使用高级排队(Advanced Queuing)并调用DBMS_AQ.DEQUEUE,解决方案会更容易。