在Oracle数据库中,DBMS_SCHEDULER是取代早期DBMS_JOB的新一代作业调度内核。它将调度定义、执行实例、权限分配统一存放在数据字典表如*_SCHEDULER_JOBS中,使DBA可以通过SQL直接查询每一次运行的开始时间、结束状态和报错堆栈。理解这套体系,是构建可靠后台定时处理逻辑的基础。

核心调度对象与基础创建方式
DBMS_SCHEDULER并不是单一存储过程,而是一组以程序、计划、作业为核心的管理包。程序(program)描述要执行什么,例如一段PL/SQL或者一个可执行的操作系统命令;计划(schedule)描述何时执行,支持日历语法如FREQ=DAILY;BYHOUR=2;作业(job)则将前两者绑定并启用。这种拆分让同一段逻辑可以在不同时间点被多个作业复用,也方便单独调整频率而不动业务代码。
下面示例创建一个每天凌晨两点调用存储过程的作业。注意ENABLED参数直接置为TRUE,作业提交后即进入调度队列。如果PL/SQL块中有绑定变量需求,应使用STORED_PROGRAM或直接写匿名块。日历表达式写错不会导致创建失败,但作业会一直处于BROKEN或永远不会触发,因此建议在测试库先用DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING验证。
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'NIGHTLY_STAT_JOB',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN pkg_stats.refresh_all; END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=DAILY;BYHOUR=2;BYMINUTE=0',
enabled => TRUE,
comments => '每日凌晨统计刷新'
);
END;
/
与旧版DBMS_JOB相比,新组件在权限上更细。普通用户默认不能创建指向操作系统可执行文件的外部作业,必须由DBA授予CREATE EXTERNAL JOB角色,且文件须位于数据库服务器允许目录对象内。这种隔离减少了恶意命令执行面,但也要求运维在部署脚本类任务时提前规划DIRECTORY对象与操作系统账号映射。
链式作业与资源控制的进阶用法
当业务需要依次执行抽取、转换、加载三步且任一步失败就中止,单作业就不够用了。DBMS_SCHEDULER提供链(chain)对象,内部用步骤(step)和规则(rule)表达依赖。每一步可指向不同程序,规则用类似WHEN step1 SUCCEEDED THEN start step2的语法描述流转。链本身挂到作业上后,调度器会自动记录每个步骤的起止,排查断点比手写状态码直观很多。
资源消费组是另一项DBMS_JOB没有的能力。通过把作业指派到特定资源组,可以限制其CPU份额,避免报表作业在白天误触发后拖垮交易库。以下代码展示如何给已有作业设置资源计划,前提是数据库已启用Resource Manager并定义了对应组。若未启用,该属性会被忽略但不报错,因此监控时不能只信配置,还要看实际会话消耗。
BEGIN
DBMS_SCHEDULER.SET_ATTRIBUTE (
name => 'NIGHTLY_STAT_JOB',
attribute => 'RESOURCE_CONSUMER_GROUP',
value => 'BATCH_GROUP'
);
END;
/
链式作业还支持事件触发。不同于时间计划,可定义作业等待某个Oracle AQ消息或文件到达信号后再跑,适合与上游系统解耦。但这种异步模型要求监听进程正常,若数据库重启后未自动恢复订阅,作业会静默挂起。建议在标准化部署脚本里显式调用ENABLE_on_startup相关属性,并配合告警查询*_SCHEDULER_RUNNING_CHAINS视图。
运行监控与常见故障排查
作业不跑或跑错时,第一手信息在*_SCHEDULER_JOB_LOG和*_SCHEDULER_JOB_RUN_DETAILS。前者记生命周期事件如CREATED、RUNNING、COMPLETED,后者存具体这次执行的报错和用时。很多人发现作业状态是FAILED却找不到原因,是因为只查了DBA_SCHEDULER_JOBS的LAST_ERROR字段,而详细堆栈仅在RUN_DETAILS中按LOG_ID关联。
权限不足是外部作业最常见的坑。如果job_type为EXECUTABLE且指向服务器脚本,运行账号是数据库安装用户而非登录DBA。当脚本要写业务目录却报权限拒绝,应检查操作系统层ACL而非数据库GRANT。另外日历表达式区分大小写,FREQ写错成freq不会被校验拦截,作业可能按默认只跑一次,这种隐蔽问题只能通过EVALUATE_CALENDAR_STRING提前暴露。
SELECT j.job_name,
l.log_date,
l.status,
d.additional_info
FROM user_scheduler_job_log l
JOIN user_scheduler_job_run_details d
ON l.log_id = d.log_id
WHERE j.job_name = 'NIGHTLY_STAT_JOB'
ORDER BY l.log_date DESC;
对于长时间未触发的作业,还要确认作业类(job_class)是否关联了正确的窗口(window)。窗口是带资源计划的时段定义,若作业被指派到只在周末打开的窗口,工作日自然沉默。综合使用字典视图与Resource Manager报表,才能把调度系统真正管起来,而不是出了问题靠手工补跑。
DBMS_SCHEDULEROracle作业调度定时任务修改时间:2026-08-18 15:32:33