在银行、电信、政企等行业的核心系统里,白天交易高峰期数据库资源紧张,大批量的数据清洗、统计汇总、归档清理、报表生成等任务只能放到夜间执行。如何保证这些任务在指定时间自动触发、按依赖顺序执行、失败后能自动重试并通知值班人员,是每个DBA和运维开发人员都要面对的课题。Oracle自带的DBMS_SCHEDULER包提供了一套完整的企业级调度方案,不依赖任何外部工具就能实现类似crontab甚至更强大的调度能力。

为什么不再推荐使用DBMS_JOB
DBMS_JOB是Oracle早期的调度工具,很多老系统里仍然能见到它的身影。它通过DBMS_JOB.SUBMIT提交任务,用interval参数控制重复执行,配置简单是它唯一的优势。但它的问题也很明显:没有原生的日志体系,任务执行历史只能靠自己建表记录;不支持任务间依赖,所有任务的先后顺序只能靠人为计算时间差来间接保证;一旦某个任务延迟,后续任务全部错位,夜间批处理很容易因此整体瘫痪。此外DBMS_JOB无法感知数据库关闭事件,实例重启后broken状态的任务还需要人工干预,对无人值守的夜间场景非常不友好。
DBMS_SCHEDULER从10g引入后逐步完善,到11g和12c已经非常成熟。它将作业、程序、调度三个对象解耦,支持基于事件触发、基于链的多任务编排、细粒度的超时控制和完整的执行日志。查询USER_SCHEDULER_JOB_RUN_DETAILS视图就能看到每次执行的开始时间、结束时间、耗时和错误码,排查问题不需要再额外埋点。对于夜间批量这种对可靠性要求高的场景,DBMS_SCHEDULER是明显更合适的选择。唯一需要注意的是,新调度器默认由调度进程自动管理,建议先确认job_queue_processes参数设置合理,避免与遗留任务产生资源冲突。
DBMS_SCHEDULER三层架构设计与基础配置
DBMS_SCHEDULER的设计思想是解耦:程序(Program)定义要执行什么,调度(Schedule)定义什么时候执行,作业(Job)把两者绑定起来。这种设计的好处是复用性强,比如十个夜间任务都可以引用同一个名为NIGHTLY_WINDOW的调度对象,以后要把执行时间从凌晨1点整体改成凌晨2点,只需修改一处即可,不必逐个任务调整,出错概率大大降低。程序对象还支持带参数,同一个存储过程可以被不同程序以不同参数复用。
下面是一个完整的创建示例,包含程序、调度和作业三个部分,可以直接在实际库上改造使用:
-- 1. 定义程序:封装要执行的内容
BEGIN
DBMS_SCHEDULER.CREATE_PROGRAM (
program_name => 'PROG_CLEAN_LOG',
program_type => 'STORED_PROCEDURE',
program_action => 'P_CLEAN_LOG',
number_of_arguments => 0,
enabled => FALSE,
comments => '清理三个月前的接口日志');
DBMS_SCHEDULER.ENABLE('PROG_CLEAN_LOG');
END;
/
-- 2. 定义调度:每天凌晨1点执行
BEGIN
DBMS_SCHEDULER.CREATE_SCHEDULE (
schedule_name => 'SCHED_NIGHTLY_01',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=DAILY;BYHOUR=1;BYMINUTE=0;BYSECOND=0',
comments => '夜间批量统一调度窗口');
END;
/
-- 3. 创建作业:绑定程序与调度
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'JOB_CLEAN_LOG',
program_name => 'PROG_CLEAN_LOG',
schedule_name => 'SCHED_NIGHTLY_01',
enabled => TRUE,
auto_drop => FALSE,
comments => '每日凌晨清理接口日志');
END;
/其中repeat_interval使用日历表达式语法,FREQ支持DAILY、WEEKLY、MONTHLY等频率,BYHOUR和BYMINUTE指定具体时刻。比如工作日每天凌晨2点30分执行可以写成FREQ=WEEKLY;BYDAY=MON,TUE,WED,THU,FRI;BYHOUR=2;BYMINUTE=30,表达能力和crontab相比毫不逊色,而且支持每月最后一天这类crontab较难表达的场景。需要注意时区问题,建议创建时统一用SYSTIMESTAMP指定start_date,跨地域系统则明确写时区后缀,避免夏令时切换导致任务提前或推迟一小时。
用Chain实现多任务依赖编排
夜间批量最核心的需求是任务依赖。比如典型流程:先同步上游数据,同步成功后跑数据校验,校验通过后并行执行多个统计任务,最后生成日报表。任何一个前置环节失败,后续任务都不应该执行,否则可能基于错误数据产出错误的报表。DBMS_SCHEDULER的链(Chain)就是为这种场景设计的,它把整个批处理流程作为一个数据库对象管理,状态持久化在数据字典中,实例重启后进度不会丢失。
链的核心概念是步骤(Step)和规则(Rule)。步骤指向具体的程序,规则定义步骤之间的流转逻辑,用返回布尔值的条件表达式决定下一步做什么。完整示例如下:
BEGIN
-- 创建链
DBMS_SCHEDULER.CREATE_CHAIN(chain_name => 'CHAIN_NIGHTLY_BATCH');
-- 定义步骤
DBMS_SCHEDULER.DEFINE_CHAIN_STEP('CHAIN_NIGHTLY_BATCH', 'STEP_SYNC',
'PROG_SYNC_UPSTREAM');
DBMS_SCHEDULER.DEFINE_CHAIN_STEP('CHAIN_NIGHTLY_BATCH', 'STEP_CHECK',
'PROG_DATA_CHECK');
DBMS_SCHEDULER.DEFINE_CHAIN_STEP('CHAIN_NIGHTLY_BATCH', 'STEP_REPORT',
'PROG_GEN_REPORT');
-- 链启动后先执行同步
DBMS_SCHEDULER.DEFINE_CHAIN_RULE('CHAIN_NIGHTLY_BATCH',
condition => 'TRUE', action => 'START STEP_SYNC');
-- 同步成功执行校验,失败则结束链
DBMS_SCHEDULER.DEFINE_CHAIN_RULE('CHAIN_NIGHTLY_BATCH',
condition => 'STEP_SYNC SUCCEEDED', action => 'START STEP_CHECK');
DBMS_SCHEDULER.DEFINE_CHAIN_RULE('CHAIN_NIGHTLY_BATCH',
condition => 'STEP_SYNC FAILED', action => 'END');
-- 校验成功生成报表后结束
DBMS_SCHEDULER.DEFINE_CHAIN_RULE('CHAIN_NIGHTLY_BATCH',
condition => 'STEP_CHECK SUCCEEDED', action => 'START STEP_REPORT, END');
DBMS_SCHEDULER.DEFINE_CHAIN_RULE('CHAIN_NIGHTLY_BATCH',
condition => 'STEP_CHECK FAILED', action => 'END');
DBMS_SCHEDULER.ENABLE('CHAIN_NIGHTLY_BATCH');
END;
/
-- 创建运行链的作业
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'JOB_NIGHTLY_BATCH',
job_type => 'CHAIN',
job_action => 'CHAIN_NIGHTLY_BATCH',
schedule_name => 'SCHED_NIGHTLY_01',
enabled => TRUE);
END;
/条件中的SUCCEEDED、FAILED、STOPPED、COMPLETED等状态可以灵活组合,还能引用步骤的返回值做分支判断。如果需要并行执行多个步骤,只需在一条规则的action里写START STEP_A, START STEP_B,调度器会并发拉起它们。相比用shell脚本串行控制,链的优势在于全部逻辑落在库内,有完整日志和运行历史,配合DBA_SCHEDULER_RUNNING_CHAINS视图可以实时观察链执行到了哪一步,定位卡点一目了然。
失败重试、超时控制与告警通知
夜间作业无人值守,失败处理机制必须提前设计好。DBMS_SCHEDULER提供了几个关键属性:通过SET_ATTRIBUTE设置max_failures控制最大失败次数,超过后作业自动进入broken状态防止反复无效重跑;对偶发锁冲突类故障,可以在存储过程内部捕获异常后返回,结合调度器重新触发实现重试;对于可能因锁等待而卡住的任务,设置max_run_duration,超时后调度器会发出JOB_OVER_MAX_DUR事件,配合事件驱动的处理作业可实现自动告警甚至强制终止。
具体的属性配置与邮件通知示例:
BEGIN
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'JOB_CLEAN_LOG',
attribute => 'max_failures', value => 3);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'JOB_CLEAN_LOG',
attribute => 'max_run_duration',
value => INTERVAL '2' HOUR);
-- 失败时发送邮件给值班人员
DBMS_SCHEDULER.ADD_JOB_EMAIL_NOTIFICATION (
job_name => 'JOB_CLEAN_LOG',
recipients => 'dba_oncall@ipipp.com',
events => 'JOB_FAILED,JOB_BROKEN',
subject => '夜间作业告警:JOB_CLEAN_LOG 执行失败',
body => '作业:%job_owner%.%job_name% 状态:%event_name% 时间:%event_timestamp%');
END;
/邮件通知依赖数据库配置过SMTP服务器,相关属性通过DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE设定。如果企业内部有自己的监控平台,更常见的做法是基于USER_SCHEDULER_JOB_RUN_DETAILS视图写一个巡检存储过程,每天早上七点汇总夜间所有作业的执行状态,把失败和超时任务统一推送到企业微信或钉钉群,值班人员上班第一眼就能看到批处理结论,而不是被动等待业务方报障。
执行窗口的资源控制与运维建议
夜间批量虽然避开了交易高峰,但多个重任务同时启动也可能把备份窗口挤占掉,甚至引发IO争用导致整体批处理变慢。一方面可以利用数据库资源管理器(Resource Manager)做限流,为批量任务分配独立的消费者组,限制其并行度和CPU占比,保证备份任务的资源下限;另一方面在编排链时注意错峰,把耗时的归档清理类任务安排在统计任务之前或之后串行执行,避免磁盘IO被打满。
此外还有几个值得坚持的运维习惯:为所有夜间作业统一命名前缀(如JOB_NIGHTLY_),便于一条SQL批量查询执行状态;定期清理DBA_SCHEDULER_JOB_RUN_DETAILS中的历史日志,防止SYSAUX表空间持续膨胀;变更上线前用DBMS_SCHEDULER.RUN_JOB手动触发一次验证链路通畅;从crontab或DBMS_JOB迁移时,建议新旧并行观察一周,确认日志无差异后再下线旧入口。按这套思路建设起来的夜间调度体系,稳定性和可观测性都会有质的提升。
Oracle批量作业DBMS_SCHEDULER作业调度修改时间:2026-09-09 03:39:13