多台服务器共享一个数据库(oracle数据库),防止同时访问一批数据

在处理数据时,经常会用定时任务间隔去跑一批数据。如果一台服务器去查询执行倒也没事,如果出于负载均衡考虑,多台服务器同时去跑这个数据库的一张表的数据时,就很有可能会取到相同数据,出现执行数据重复的问题。

贴出我的做法。

--创建表定时任务取模求余表记录表
create table T_TASKMODREMAINDER
(
 TASKNAME  varchar2(100) ,
 MODREMAINDER number(3) ,
 UPDATESEQ number(20) default 0,
 TASKIP  VARCHAR2(32),
 UPDATETIME date
)
/

-- Add comments to the columns 
comment on column T_TASKMODREMAINDER.TASKNAME
  is '定时任务类名称'
/
comment on column T_TASKMODREMAINDER.MODREMAINDER
  is '定时任务执行时使用取模余数。必须从0开始,按1递增并保持连续。备注:每台机器执行定时任务时,首先要获取updatetime时间最早的该余数并对该记录上锁(如果获取失败则获取下updatetime次早的余数,直如果所有余数都获取失败,定时任务返回),然后使用该余数从数据库获取数据,获取完毕提交或失败回滚时自动释放记录锁。'
/
comment on column T_TASKMODREMAINDER.UPDATESEQ
  is '按updateseq从小到大的顺序获取取模余数。获取余数上锁后,先更新此字段,然后再获取业务数据。'
/
comment on column T_TASKMODREMAINDER.TASKIP
  is '当前使用该余数的机器IP,如:10.10.10.10。备注:1.并不是只有该IP才可以使用该余数,而是谁获取到该余数则在此登记。2.IP在使用余数时自动更新,初始化脚本不需要填写'
/
comment on column T_TASKMODREMAINDER.UPDATETIME
  is '使用该余数的机器最近执行任务的时间。'
/
---初始化表插入数据,有三台服务器,则插入三条数据,MODREMAINDER从0开始







insert into T_TASKMODREMAINDER (TASKNAME, MODREMAINDER, UPDATESEQ, TASKIP, UPDATETIME)
values ('BatchOperateTask', 0, 0, '', sysdate)
/
insert into T_TASKMODREMAINDER (TASKNAME, MODREMAINDER, UPDATESEQ, TASKIP, UPDATETIME)
values ('BatchOperateTask', 1, 0, '', sysdate)
/
insert into T_TASKMODREMAINDER (TASKNAME, MODREMAINDER, UPDATESEQ, TASKIP, UPDATETIME)
values ('BatchOperateTask', 2, 0, '', sysdate)
/
commit
/
--创建T_TASKMODREMAINDER 表的序列
create sequence seq_TASKMODREMAINDER start with 1 increment by 1
/

--创建取模函数
create or replace function f_func_gettaskmodremainder
(
    str_in_taskname    in     T_TASKMODREMAINDER.TASKNAME%type,     --定时任务名称
    i_o_modebase       out    number,                               --取模基数
    i_o_remainder      out    number                                --取模余数
) return integer
--返回值:
--  0:异常 
--  1:成功 
--  2:取模余数全部被其他机器锁定
--  3:取模余数记录和取模基数不匹配 
--  4:无定时任务配置数据

------=========        注意       ================
--1、本存储过程执行过后,则会锁定某条取模余数记录(行锁),
--   调用程序在退出之前请一定要提交或回滚事务时以释放该行锁。
--2、调用方存储过程内只要一提交或回滚则会立即释放该行锁,
--   所以,调用方获取数据的模式为:获取锁、更新数据、提交,不允许出现获取锁、更新数据、提交、再获取数据的情况
--3、取数据过程中必须更新数据状态,防止多个定时任务重复获取数据或因竞争数据引发死锁
--4、调用方在所有更新相同表数据的地方都必须采用取模方式更新数据。

is
    --自定义异常,用于标志因资源忙导致锁定记录不成功
    resource_busy    exception;

    --将锁定记录异常和名字resource_busy绑定
    pragma exception_init(resource_busy, -54);

    i_l_remaindercount   number;
    i_l_locksuccess      number := 0;
    dt_l_nowtime         date := sysdate;
    i_l_return           number;
    i_l_updateseq        number;

begin

    --获取取模基数(获取数据定时任务总共有几个定时任务)
    begin         
         select configvalue into i_o_modebase from t_config where configname = 'portal_task_count'; --可以写死总共定时任务,为了使用方便可配置
    exception
        when no_data_found then
            --无定时任务配置数据
            return 4;
    end;

    --检查取模余数记录是否正常
    select count(*)
      into i_l_remaindercount
      from t_taskmodremainder
     where taskname = str_in_taskname
       and modremainder >=0
       and modremainder < i_o_modebase;
       
    --如果取模余数记录和取模基数不匹配,异常退出
    if ( i_l_remaindercount <> i_o_modebase ) then
       return 3;   
    end if;
    
    --获取获取数据序列
    select SEQ_TASKMODREMAINDER.nextval
      into i_l_updateseq
      from dual;
      
    --对于序列用完又重头开始的情况,需要将大于当前序列的记录更新为1,每次仅处理一条
    begin
        select modremainder
          into i_o_remainder
          from t_taskmodremainder
         where taskname = str_in_taskname
           and updateseq > i_l_updateseq
           and rownum = 1
           for update nowait;
        
           --获取成功,更新
           update t_taskmodremainder
              set updateseq = 1
            where taskname = str_in_taskname
              and modremainder = i_o_remainder;
                
    exception
        --如果没有从头开始的情况,不处理
        when no_data_found then
            null;
        --锁定不成功,不处理
        when resource_busy then
            null;
    end;  
      
    --按updateseq从小到大的顺序获取取模余数记录并上锁
    for i in 1..i_o_modebase loop
        begin
          select modremainder
            into i_o_remainder
            from t_taskmodremainder
           where taskname = str_in_taskname
             and modremainder = ( select modremainder
                                      from ( select modremainder, rownum rn
                                               from ( select modremainder from t_taskmodremainder
                                                       where taskname = str_in_taskname
                                                         and modremainder < i_o_modebase
                                                       order by updateseq
                                                    )
                                           )
                                     where rn = i
                                  )
             for update nowait;

             --锁定记录成功,更新
             update t_taskmodremainder
                set updatetime = dt_l_nowtime,
                    taskip = sys_context ('userenv', 'ip_address'),
                    updateseq = i_l_updateseq
              where taskname = str_in_taskname
                and modremainder = i_o_remainder;
             
             --设置锁定成功标志,跳出循环
             i_l_locksuccess := 1;
             exit;

        exception
            --锁定不成功,继续锁定updatetime次旧的一个
            when resource_busy then
                null;
        end;

    end loop;

    --如果锁定取模余数记录不成功,返回2,成功返回1
    return case i_l_locksuccess
               when 1 then 1
               else 2
           end;

exception
    when others then
         return 0;
end f_func_gettaskmodremainder;
/

=========================================

--创建存储数据表
create table t_batchoperatedata
(
  id            number primary key,
  username      varchar2(20) ,
  userid        varchar2(20) ,
  insertTime   date default sysdate,
  status        number ,
  roleCode      varchar2(20) ,
  batchid       number default -1,
  operatertime date
)
/
-- Add comments to the table 
comment on table t_batchoperatedata
  is '存储数据表'
/
comment on column t_batchoperatedata.username
  is '用户名'
/
comment on column t_batchoperatedata.userid
  is '用户id'
/
comment on column t_batchoperatedata.insertTime
  is '入库时间'
/
comment on column t_batchoperatedata.status
  is '当前数据状态 1.未处理;2.已处理;3已获取'
/

comment on column t_batchoperatedata.roleCode
  is '角色编号'
/


--获取批量执行数据
create or replace function f_getoperatedata
(
str_taskname in T_TASKMODREMAINDER.taskname%type, --定时任务名称
i_o_result   out integer
)

 return pkg_type.refcur --定义返回类型
 is
  results pkg_type.refcur; --声明游标

  get_batch_execute_maxs varchar2(20); --最大获取条数

  i_o_modebase  number; --模基数
  i_o_remainder number; --模余数
  i_funcresult  integer; --函数结果输出参数
  temp_id number;
  t_batchid     number;

begin

      --获取模基数与本次抢占到的模基数
    i_funcresult := f_func_gettaskmodremainder(  str_taskname,
                                                 i_o_modebase,
                                                 i_o_remainder
                                              );

    --对返回码处理
    begin
        case i_funcresult
            when 1 then
                i_o_result := 1;
            when 2 then
                i_o_result := 1;
            when 3 then
                i_o_result := 2;
            when 4 then
                i_o_result := 3;
            else
                i_o_result := 0;
         end case;
    exception
        when others then
        i_o_result := 0;
    end;

       if i_funcresult != 1 then
        rollback;
        open results for select * from dual where 2=1;
        return results;
    end if;

  --获取每次最大处理条数
  select configvalue
    into get_batch_execute_maxs
    from t_config
   where configname =
         'get_batch_execute_maxs';

   select seq_batchid.nextval into  t_batchid from dual;

   update t_batchoperatedata b
      set batchid = t_batchid, status = 3,operatertime=sysdate
    where rownum <= get_batch_execute_maxs
      and b.status = 1
	order by b.insertTime;

  open results for

  --查询出要处理的没个号码
    select b.id,                   --id
           b.username,           --用户名
           b.userid,            --用户id
           b.insertTime,           --入库时间
           b.roleCode,        --操作者角色账号
      from t_batchoperatedata b
     where b.batchid=t_batchid;
  commit;
  return results;
exception
  when others then
    return results;
end f_getoperatedata;
/

在类中调用f_getoperatedata函数来获取要操作的数据,每次在函数f_getoperatedata中都会调用f_func_gettaskmodremainder函数获取模基数与本次抢占到的模基数,并且锁定t_taskmodremainder
表,防止其他服务器定时任务使用,确保每台服务器定时任务每次获取的数据不是同一批。

如果有某台服务器的定时任务获取到数据后,并且以修改表t_batchoperatedata中status = 3时,服务器濡染荡掉。那么可以再加一个定时任务来定时判断如果operatertime时间大于你认为超时的时间,则去修改t_batchoperatedata中status = 3的数据,全部修改状态为1,等待下次继续执行。

或者可直接在数据库中加个job任务来执行,认为数据库job较方便些,还省去写java代码。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值