摘要:本文通过实验和事件跟蹤来分析Oracle Job执行过程中修改下次执行时间的机制
有些人问Oracle的JOB在设定完next_date和interval之后到底是什么时候决定下一次运行时间的可以归纳成以下几个问题
. 假设我们的JOB设定第一次运行的时间是:运行的间隔是小时JOB运行需要耗时分钟那么第二次运行是在:还是:?
. 如果是在:那是不是说明只要JOB一开始运行next_date就被重新计算了?
. JOB的下一次运行会受到上一次运行时间的影响吗?如果受到影响如何可以避免这个影响而让JOB在每天的指定时刻运行?
本文通过一些实验和跟蹤来解释上面的所有问题
首先我们选择一个测试用户假设该用户名为kamus
由于我们在实验用的存储过程中会用到dbms_lock包所以需要由sys用户先授予kamus用户使用dbms_lock包的权限
d:\Temp>sqlplus / as sysdba
SQL*Plus: Release Production on 星期三 月 ::
Copyright (c) Oracle Corporation All rights reserved
连接到:
Oraclei Enterprise Edition Release Production
With the Partitioning OLAP and Oracle Data Mining options
JServer Release Production
SQL> grant execute on dbms_lock to kamus;
授权成功
然后用kamus用户登录数据库创建我们测试使用的存储过程sp_test_next_date
create or replace procedure sp_test_next_date as
p_jobno number;
P_nextdate date;
begin
将调用此存储过程的job的next_date设置为分钟以后
select job into p_jobno from user_jobs where what = sp_test_next_date;;
execute immediate begin dbms_jobnext_date( || to_char(p_jobno) || sysdate+/);commit;end;;
修改完毕以后检查user_jobs视图输出job目前的next_date
select next_date
into P_nextdate
from user_jobs
where what = sp_test_next_date;;
dbms_outputput_line(JOB执行中的next_date: ||
to_char(p_nextdateYYYYMMDD HH:MI:SS));
等待秒再退出执行
dbms_locksleep(seconds => );
end sp_test_next_date;
创建调用该存储过程的JOB定义interval为每天一次也就是这次执行以后下次执行时间应该在天以后
SQL> variable jobno number;
SQL> BEGIN
DBMS_JOBSUBMIT(job => :jobno
what => sp_test_next_date;
next_date => SYSDATE
interval => SYSDATE+);
COMMIT;
END;
/
PL/SQL 过程已成功完成
jobno
然后我们手工执行存储过程执行完毕以后再手工从user_jobs视图中获得JOB的下次执行时间可以看到在存储过程中修改的JOB的下次执行时间已经生效变成了当前时间的分钟以后而不是默认的天以后
SQL> conn kamus
请输入口令:
已连接
SQL> set serverout on
SQL> exec sp_test_next_date();
JOB执行中的next_date: ::
PL/SQL 过程已成功完成
SQL> col next_date for a
SQL> select to_char(next_dateYYYYMMDD HH:MI:SS) next_date from user_jobs
where what = sp_test_next_date;;
NEXT_DATE
我们再手工运行JOB看看这次的结果可以发现JOB没有运行完毕以前被修改了的下次运行时间跟JOB运行完毕以后再次手工检索user_jobs视图获得的下次运行时间已经不相同了由此我们可以得出一个结论next_date是在JOB运行完毕以后被Oracle自动修改的而不是在JOB刚开始运行的时候因为我们在存储过程中修改的next_date在JOB运行结束之后又被修改为默认的天以后了
SQL> exec dbms_jobrun();
JOB执行中的next_date: ::
PL/SQL 过程已成功完成
SQL> select to_char(next_dateYYYYMMDD HH:MI:SS) next_date from user_jobs
where what = sp_test_next_date;;
NEXT_DATE
现在我们再次修改存储过程输出存储过程开始执行的时间便于跟执行完毕以后的JOB下次执行时间进行比较
create or replace procedure sp_test_next_date as
p_jobno number;
P_nextdate date;
begin
输出JOB刚开始执行的时间
dbms_outputput_line( JOB开始执行的时间: ||
to_char(sysdate YYYYMMDD HH:MI:SS));
将调用此存储过程的job的next_date设置为分钟以后
select job into p_jobno from user_jobs where what = sp_test_next_date;;
execute immediate begin dbms_jobnext_date( || to_char(p_jobno) || sysdate+/);commit;end;;
修改完毕以后检查user_jobs视图输出job目前的next_date
select next_date
into P_nextdate
from user_jobs
where what = sp_test_next_date;;
dbms_outputput_line( JOB执行中的next_date: ||
to_char(p_nextdateYYYYMMDD HH:MI:SS));
等待秒再退出执行
dbms_locksleep(seconds => );
end sp_test_next_date;
重新进行测试我们可以发现JOB的next_date是JOB开始执行时间的天以后而不是JOB结束时间的天以后(因为JOB结束需要经过秒钟)
SQL> exec dbms_jobrun();
JOB开始执行的时间: ::
JOB执行中的next_date: ::
PL/SQL 过程已成功完成
SQL> select to_char(next_dateYYYYMMDD HH:MI:SS) next_date from user_jobs
where what = sp_test_next_date;;
NEXT_DATE
至此我们已经说明了两个问题就是JOB在运行结束之后才会更新next_date但是计算的方法是JOB刚开始的时间加上interval设定的间隔
下面我们通过trace来再次求证这个结论
SQL> ALTER SESSION SET EVENTS trace name context forever level ;
会话已更改
SQL> exec dbms_jobrun();
PL/SQL 过程已成功完成
SQL> ALTER SESSION SET EVENTS trace name context off;
会话已更改
执行完毕以后在udump目录中查看生成的trace文件如果我们用tkprof来格式化这个trace文件然后再查看格式化后的结果我们会感到很诧异因为在格式化完毕的SQL执行顺序中更新job$表的语句出现在dbms_jobnext_date语句之前也就是看上去是Oracle先按照interval自动更新了JOB的next_date然后才继续往下执行存储过程中定义的next_date更新语句而这样显然无法解释我们在上面的实验中看到的结果
但是当我们跳过tkprof而直接去查看生成的trace文件就会恍然大悟同时也印证了steve adams在ixora上提到的观点tkprof格式化完的结果会省略一些信息甚至在有时候会给我们错误的信息
直接查看trace文件我们可以看到如下的执行顺序
. parse cursor #(oracle根据interval和先前保存的this_date字段值更新job$表的语句包括更新failures last_date next_date total等)
. parse cursor #(存储过程中的begin dbms_jobnext_date语句)
. binds cursor #(将加上了分钟的时间绑定到cursor #上)
. exec cursor #(执行cursor #)
. wait cursor #(经历一个PL/SQL lock timer事件也就是存储过程中执行的dbms_locksleep方法)
. binds cursor #(将JOB刚开始执行时候的时间绑定到cursor #上)
. exec cursor #(执行cursor #)
也就是说虽然更新job$的语句被很早地解析过了但是直到JOB运行结束时这个被解析过的游标才开始作变量绑定进而开始执行
正是因为解析update sysjob$语句的时间早于解析begin dbms_jobnext_date语句的时间所以tkprof的结果将前者放在了前面
由于trace文件过长所以不在本文中贴出了如果有兴趣可以发邮件给我我的邮件地址是
本文的最后一部分解答本文开头提出的第三个问题也就是
JOB的下一次运行会受到上一次运行时间的影响吗?如果受到影响如何可以避免这个影响而让JOB在每天的指定时刻运行?
JOB的下一次运行时间是会受上一次影响的如果我们的interval仅仅是sysdate+/这样的形式的话无疑上次执行的时间再加上小时就