背景:在电信软件领域,一个软件常常有多个portal,对于话单的处理常常用多个定时任务来处理,那么问题来了,如何保证每个portal的每个定时任务取得话单都是各不相同的呢? 有人会问,为什么不在取数据的Java侧进行同步控制呢,但是问题是多个portal,很明显这不是Java侧的同步能控制的。
基础:oracle的for update关键字是一个行级排它锁,也就是说for update只会排他锁定相关的行,并不会锁定整个表,在锁定期间,其他线程可以读取,但不能再加其他排它锁了,任何尝试加排它锁的线程都将阻塞等待该锁的释放(注:for update nowait不会等待,直接返回错误)。oracle遇到for update的语句就会自动进入事务(和 update操作是一个效果),我们可以做一个简单的实验,在pl/sql dev中写“select * from TICKET t where t.id = 1 for update;”,执行它会发现左上角出现了事务提交和事务回滚的按钮。
实现:那么我们同步获取话单数据就可以利用for update加排它锁的特性,来实现同步取数据,实现如下:
create or replace procedure p_sycn_getoneticket
(
dt_in_createtime in date, --createtime
cur_tickets out sys_refcursor --return cursor
) as --定义数组实体,用于后边接受数据
nstb_ids nstb_numbers_type := new nstb_numbers_type();
begin
--取数据
select t.id
bulk collect into nstb_ids
from ticket t
where t.createtime < dt_in_createtime
and t.status = 1
for update;
--更新数据状态,避免被其他线程获取到
update ticket t
set t.status = 2
where t.createtime < dt_in_createtime
and t.status = 1;
--提交,释放update锁
commit;
--将数组数据放入游标,用于返回
open cur_tickets for
select t1.id, t1.price, t1.des, t1.status, t1.createtime
from ticket t1, table(cast (nstb_ids as nstb_numbers_type)) t2
where t1.id = t2.column_value;
exception
when others then
rollback;
--返回空游标
open cur_tickets for
select 'X'
from dual
where 1=1;
end;
实现说明:在多线程同时执行该段代码的时候
①线程1先获取到该条记录的行级排它锁,其他线程进入等待状态;
②线程1更新数据状态,并提交,释放锁;
③线程2获取到数据的排它锁,但发现检索发现并不是符合条件的数据,重新检索数据,进而锁定另一部分数据。
如此往复,形成了该段代码对于同一数据行的同步取。