利用ORACLE JOB 模拟多线程应用
最近在做一个信息集成发布的项目,主要功能是根据用户输入的集成条件实现数据的过滤和目标数据的生成。由于数据量比较大,而且数据来源都是生产环境下的,完全实时的方式会对生成数据库造成压力,项目组考虑使用ORACLE存储过程的方式来实现,采用多个ORACLE JOB来模拟实现多线程的方式完成。
为了简单起见,前台把启动任务的相关参数信息写入到一张表,如下为表的结构:
1: --任务基础信息表
2: create table log_backstage_task (
3: id number, --任务日志主键
4: begin_time date,--任务开始时间
5: end_time date, --任务结束时间
6: start_type number(2),--启动类型 0 定时启动 1 人工启动
7: status number(1), --任务状态 0 未启动 1 执行中 2 执行完成 3 执行失败
8: outline varchar2(400),--任务过程描述
9: task_parameter clob --任务参数列表
10: );
11: --JOB模拟线程状态表
12: create table t_job_info (
13: job_number number , --主键 直接对应为ORACLE 的JOB编号
14: create_time date , --JOB创建时间
15: isbusy number(1) default 0 --是来标识JOB是否正在运行
16: );
17: --测试用数据表
18: create table DEMO1
19: (
20: STR VARCHAR2(100)
21: );
于是根据JAVA里面的多线程编程思想,用主线程接收请求,然后看有无空闲子线程,有的话则启动子线程,让子线程接管待处理的请求。
1: --package 代码
2: create or replace package pk_full_integrate is
3: --定义并发处理的JOB数量 默认配置为5
4: MAX_JOB_INSTANCE constant integer := 5;
5: --放空过程 用于子线程JOB创建后的默认执行过程
6: procedure pro_null ;
7: --主JOB调用过程
8: procedure pro_job_schedule_main;
9: --子线程 执行过程..
10: procedure pro_job_execute_main(pJobNum number,pLog_id number );
11: end pk_full_integrate;
12: /
1: --package body 代码
2: create or replace package body pk_full_integrate is
3: --空过程 用于子线程JOB创建后的默认执行过程
4: procedure pro_null as
5: begin
6: null;
7: end;
8: --判断是否还有空闲的JOB可以使用 有则返回JOB编号,没有则返回空
9: function fun_isHavaNullJob return number is
10: iResult number := null ;
11: begin
12: select m.job_number into iResult
13: from t_job_info m where m.isbusy = 0 and rownum <2;
14: return iResult;
15: exception when others then
16: return null ;
17: end;
18: --寻找最先提交的还没有启动的任务 有则返回ID,没有则返回空...
19: function fun_isHaveNotStartTask return number is
20: iResult number := null ;
21: begin
22: select t.id into iResult from (
23: select id from log_backstage_task m where m.status = 0 order by id asc ) t
24: where rownum < 2 ;
25: return iResult;
26: exception when others then
27: return null ;
28: end;
29: --主JOB调用过程
30: procedure pro_job_schedule_main is
31: ptempJob_num number := null ;
32: ptempTask_num number := null;
33: pExecSql varchar2(500) := '';
34: begin
35: ptempJob_num := fun_isHavaNullJob;
36: ptempTask_num := fun_isHaveNotStartTask ;
37: while (ptempJob_num is not null ) and ( ptempTask_num is not null ) loop
38: begin
39: pExecSql := 'pk_full_integrate.pro_job_execute_main('||ptempJob_num||','||ptempTask_num||' );' ;
40:
41: update t_job_info m set m.isbusy = 1 where m.job_number = ptempJob_num;
42: update log_backstage_task s set s.status = 1 where s.id = ptempTask_num ;
43: --dbms_job.run(ptempJob_num);
44: --改用dbms_job.broken函数,防止JOB出错之后无法重新启动,采用延迟10秒之后启动
45: dbms_job.what(ptempJob_num,pExecSql);
46: dbms_job.broken(ptempJob_num,false,sysdate+1/8640);
47: commit;
48: ptempJob_num := fun_isHavaNullJob;
49: ptempTask_num := fun_isHaveNotStartTask ;
50: exception when others then
51: dbms_output.put_line('error'||substr(sqlerrm,1,255));
52: rollback;
53: end ;
54: end loop;
55: end;
56:
57: --子线程 执行过程..
58: procedure pro_job_execute_main(pJobNum number,pLog_id number ) is
59: begin
60: --dbms_output.put_line('executed successful ........ '||pJobNum);
61: insert into demo1 values ('executed successful ........ '||pJobNum);
62: update log_backstage_task set status = 1 where id = pLog_id ;
63: update t_job_info set isBusy = 0 where job_number = pJobNum ;
64: commit;
65: exception when others then
66: rollback;
67: update log_backstage_task set status = 4 where id = pLog_id ;
68: update t_job_info set isBusy = 0 where job_number = pJobNum ;
69: commit;
70: end;
71:
72: begin
73: --包初始化脚本
74: --初始化JOB信息
75: select count(1) into pJob_cnt from t_job_info ;
76: if pJob_cnt < MAX_JOB_INSTANCE then
77: for i in 1..MAX_JOB_INSTANCE-pJob_cnt loop
78: --子线程JOB的时间间隔调整为356000(1000年),理论上不会自动执行
79: dbms_job.submit( pJob_num,'pk_full_integrate.pro_null;',sysdate,'sysdate+356000');
80: insert into t_job_info (JOB_NUMBER) values (pJob_num) ;
81: end loop;
82: end if;
83: commit;
84: dbms_output.put_line('job init success......');
85: exception when others then
86: dbms_output.put_line('occer errors,job init failure...');
87: end pk_full_integrate;
88: /
测试代码,查看执行结果,模拟成功! 呵呵。。
1: executed successful ........ 125
2: executed successful ........ 125
3: executed successful ........ 125
4: executed successful ........ 126
5: executed successful ........ 126
6: executed successful ........ 127
7: executed successful ........ 127
8: executed successful ........ 128
9: executed successful ........ 128
10: executed successful ........ 129
11: executed successful ........ 129
这里用到了dbms_job包的submit,what和broken函数,用法如下:
1. Broken()过程更新一个已提交的工作的状态,典型地是用来把一个已broken的job标记为未broken工作。
这个过程有三个参数:job 、broken与next_date。
Broken (job IN binary_integer,
Broken IN boolean,
next_date IN date :=SYSDATE)
job参数是工作号,它在问题中唯一标识工作。
broken参数指示此工作是否将标记为broken——TRUE说明此工作将标记为停止执行,而FLASE说明此工作将标记为正常执行。
next_date参数指示在什么时候此工作将再次运行。此参数缺省值为当前日期和时间。
使用Submit()过程,工作被正常地计划好。
这个过程有五个参数:job、what、next_date、interval与no_parse。
2. Submit ( job OUT binary_ineger,
What IN varchar2,
next_date IN date,
interval IN varchar2,
no_parse IN booean:=FALSE)
job参数是由Submit()过程返回的binary_ineger。这个值用来唯一标识一个工作。
what参数是将被执行的PL/SQL代码块。
next_date参数指识何时将运行这个工作。
interval参数何时这个工作将被重执行。
no_parse参数指示此工作在提交时或执行时是否应进行语法分析——TRUE
指示此PL/SQL代码在它第一次执行时应进行语法分析,
而FALSE指示本PL/SQL代码应立即进行语法分析。
3
What()过程应许在工作执行时重新设置此正在运行的命令。这个过程接收两个参数:job与what。
PROCEDURE What (job IN binary_ineger,
What IN OUT varchar2)
job参数标识一个存在的工作。what参数指示将被执行的新的PL/SQL代码。
这里使用了动态SQL用来接收任务ID和JOB编号,实现动态分配,貌似在ORACLE 10G中已经对DBMS_JOB包进行了升级,有了新的DBMS_SHEDULE包,有时间再研究一下。