Oracle数据库提供了多种方式实现存储过程的定时自动化运行。
以下是几种常用的方案:
✅ 方案1:使用 Oracle Scheduler(推荐)
这是Oracle 10g及以上版本内置的作业调度器,功能最强大、最稳定。
1.1 创建Scheduler Job(最简单)
BEGIN
-- 创建每天凌晨1点执行的Job
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'JOB_LOAD_STK_INDX_DAILY',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN PROC_LOAD_STK_MKT_QUOT_INDX; END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=DAILY; BYHOUR=1; BYMINUTE=0; BYSECOND=0',
enabled => TRUE,
comments => '每天凌晨1点执行股票指标数据装载'
);
END;
/
1.2 更灵活的调度配置
BEGIN
-- 删除已存在的Job(如果需要重建)
BEGIN
DBMS_SCHEDULER.DROP_JOB('JOB_LOAD_STK_INDX_DAILY', TRUE);
EXCEPTION
WHEN OTHERS THEN NULL;
END;
-- 创建复杂的调度Job
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'JOB_LOAD_STK_INDX_DAILY',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN
DBMS_OUTPUT.PUT_LINE(''开始执行股票指标装载...'' || TO_CHAR(SYSDATE, ''YYYY-MM-DD HH24:MI:SS''));
PROC_LOAD_STK_MKT_QUOT_INDX;
DBMS_OUTPUT.PUT_LINE(''执行完成!'');
END;',
start_date => TRUNC(SYSDATE) + INTERVAL '1' HOUR, -- 今天凌晨1点开始
repeat_interval => 'FREQ=DAILY; INTERVAL=1', -- 每天执行
enabled => FALSE, -- 先不启用
comments => '股票指标数据自动化装载作业'
);
-- 启用Job
DBMS_SCHEDULER.ENABLE('JOB_LOAD_STK_INDX_DAILY');
DBMS_OUTPUT.PUT_LINE('Job创建成功!');
END;
/
1.3 查看和管理Scheduler Job
-- 查看所有Job
SELECT job_name, enabled, state, last_start_date, next_run_date, run_count, failure_count
FROM USER_SCHEDULER_JOBS
WHERE job_name LIKE '%STK_INDX%';
-- 查看Job运行历史
SELECT job_name, log_date, status, error#
FROM USER_SCHEDULER_JOB_LOG
WHERE job_name = 'JOB_LOAD_STK_INDX_DAILY'
ORDER BY log_date DESC;
-- 查看Job运行详情
SELECT log_id, log_date, status, error#, req_start_date, actual_start_date, run_duration
FROM USER_SCHEDULER_JOB_RUN_DETAILS
WHERE job_name = 'JOB_LOAD_STK_INDX_DAILY'
ORDER BY log_date DESC;
-- 立即执行Job
BEGIN
DBMS_SCHEDULER.RUN_JOB('JOB_LOAD_STK_INDX_DAILY');
END;
/
-- 停止Job
BEGIN
DBMS_SCHEDULER.STOP_JOB('JOB_LOAD_STK_INDX_DAILY');
END;
/
-- 禁用Job
BEGIN
DBMS_SCHEDULER.DISABLE('JOB_LOAD_STK_INDX_DAILY');
END;
/
-- 删除Job
BEGIN
DBMS_SCHEDULER.DROP_JOB('JOB_LOAD_STK_INDX_DAILY');
END;
/
1.4 创建带监控和日志的增强版Job
-- 首先创建日志表
CREATE TABLE STK_INDX_JOB_LOG (
LOG_ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
JOB_NAME VARCHAR2(100) NOT NULL,
START_TIME TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
END_TIME TIMESTAMP,
STATUS VARCHAR2(20) DEFAULT 'RUNNING' CHECK (STATUS IN ('RUNNING', 'SUCCESS', 'ERROR', 'WARNING')),
ROW_COUNT NUMBER,
ERROR_MSG VARCHAR2(4000),
DURATION_SEC NUMBER,
CREATED_DATE DATE DEFAULT SYSDATE
);
-- 创建增强版存储过程
CREATE OR REPLACE PROCEDURE PROC_LOAD_STK_INDX_WITH_LOG
AS
v_job_name VARCHAR2(100) := 'JOB_LOAD_STK_INDX_DAILY';
v_log_id NUMBER;
v_row_count NUMBER;
v_start_time TIMESTAMP;
v_end_time TIMESTAMP;
BEGIN
v_start_time := SYSTIMESTAMP;
-- 插入开始日志
INSERT INTO STK_INDX_JOB_LOG (JOB_NAME, START_TIME, STATUS)
VALUES (v_job_name, v_start_time, 'RUNNING')
RETURNING LOG_ID INTO v_log_id;
DBMS_OUTPUT.PUT_LINE('Job [' || v_job_name || '] 开始执行,日志ID: ' || v_log_id);
-- 执行核心装载逻辑
PROC_LOAD_STK_MKT_QUOT_INDX;
-- 获取行数(需要修改原存储过程使其返回行数)
v_row_count := 0; -- 这里需要从存储过程获取实际行数
v_end_time := SYSTIMESTAMP;
-- 更新成功日志
UPDATE STK_INDX_JOB_LOG
SET END_TIME = v_end_time,
STATUS = 'SUCCESS',
ROW_COUNT = v_row_count,
DURATION_SEC = ROUND(EXTRACT(SECOND FROM (v_end_time - v_start_time)) +
EXTRACT(MINUTE FROM (v_end_time - v_start_time)) * 60, 3)
WHERE LOG_ID = v_log_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Job执行成功,耗时: ' ||
ROUND(EXTRACT(SECOND FROM (v_end_time - v_start_time)) +
EXTRACT(MINUTE FROM (v_end_time - v_start_time)) * 60, 3) || '秒');
EXCEPTION
WHEN OTHERS THEN
v_end_time := SYSTIMESTAMP;
-- 更新错误日志
UPDATE STK_INDX_JOB_LOG
SET END_TIME = v_end_time,
STATUS = 'ERROR',
ERROR_MSG = SQLERRM,
DURATION_SEC = ROUND(EXTRACT(SECOND FROM (v_end_time - v_start_time)) +
EXTRACT(MINUTE FROM (v_end_time - v_start_time)) * 60, 3)
WHERE LOG_ID = v_log_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Job执行失败: ' || SQLERRM);
RAISE;
END;
/
-- 创建调度Job
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'JOB_STK_INDX_ENHANCED',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN PROC_LOAD_STK_INDX_WITH_LOG; END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=30', -- 每天凌晨2:30
enabled => TRUE,
comments => '带日志监控的股票指标装载作业'
);
END;
/
✅ 方案2:使用传统的 DBMS_JOB(兼容旧版本)
-- 1. 提交Job(立即执行,每天重复)
DECLARE
v_jobno NUMBER;
BEGIN
DBMS_JOB.SUBMIT(
job => v_jobno,
what => 'BEGIN PROC_LOAD_STK_MKT_QUOT_INDX; END;',
next_date => SYSDATE, -- 立即执行
interval => 'SYSDATE + 1', -- 每天执行
no_parse => FALSE
);
COMMIT;
DBMS_OUTPUT.PUT_LINE('Job提交成功,Job编号: ' || v_jobno);
END;
/
-- 2. 查看Job
SELECT job, what, last_date, next_date, interval, broken, failures
FROM USER_JOBS;
-- 3. 修改Job执行时间
BEGIN
DBMS_JOB.INTERVAL(123, 'SYSDATE + 1/24'); -- 改为每小时执行
COMMIT;
END;
/
-- 4. 删除Job
BEGIN
DBMS_JOB.REMOVE(123);
COMMIT;
END;
/
✅ 方案3:使用操作系统的定时任务 + SQL脚本
3.1 创建执行脚本
Windows (stk_indx_job.bat):
@echo off
set ORACLE_HOME=C:\app\oracle\product\19.0.0\dbhome_1
set PATH=%ORACLE_HOME%\bin;%PATH%
set ORACLE_SID=ORCL
set USERNAME=your_user
set PASSWORD=your_password
echo 开始执行股票指标装载作业: %date% %time%
sqlplus -S %USERNAME%/%PASSWORD%@%ORACLE_SID% @C:\scripts\stk_indx_job.sql
echo 作业执行完成: %date% %time%
stk_indx_job.sql:
SET SERVEROUTPUT ON
SET FEEDBACK OFF
SET VERIFY OFF
SET ECHO OFF
BEGIN
DBMS_OUTPUT.PUT_LINE('开始执行股票指标装载: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
PROC_LOAD_STK_MKT_QUOT_INDX;
DBMS_OUTPUT.PUT_LINE('执行完成: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('执行失败: ' || SQLERRM);
RAISE;
END;
/
EXIT;
Linux (stk_indx_job.sh):
#!/bin/bash
export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1
export PATH=$ORACLE_HOME/bin:$PATH
export ORACLE_SID=ORCL
echo "开始执行股票指标装载作业: $(date)"
sqlplus -S your_user/your_password@ORCL <<EOF
SET SERVEROUTPUT ON
SET FEEDBACK OFF
BEGIN
DBMS_OUTPUT.PUT_LINE('开始执行: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
PROC_LOAD_STK_MKT_QUOT_INDX;
DBMS_OUTPUT.PUT_LINE('执行完成: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/
EXIT;
EOF
echo "作业执行完成: $(date)"
3.2 配置操作系统的定时任务
Windows 任务计划程序:
- 打开"任务计划程序"
- 创建基本任务
- 设置触发器(每天凌晨1点)
- 操作:启动程序
C:\scripts\stk_indx_job.bat - 设置条件和工作目录
Linux Cron:
# 编辑crontab
crontab -e
# 添加定时任务(每天凌晨1点执行)
0 1 * * * /home/oracle/scripts/stk_indx_job.sh >> /home/oracle/scripts/job.log 2>&1
# 查看crontab
crontab -l
✅ 方案4:结合Kettle和数据库调度
4.1 在Kettle中创建作业调度
在Kettle中创建作业,包含:
- 开始步骤
- 执行SQL脚本(调用存储过程)
- 成功/失败处理
- 邮件通知(可选)
然后使用Kettle的作业调度功能或操作系统的定时任务调用Kettle作业。
4.2 使用数据库Job调用Kettle转换
-- 创建调用Kettle的存储过程
CREATE OR REPLACE PROCEDURE PROC_RUN_KETTLE_JOB
AS
v_job_path VARCHAR2(500) := '/home/kettle/jobs/load_stk_indx.kjb';
BEGIN
-- 通过Java或外部程序调用Kettle
DBMS_OUTPUT.PUT_LINE('调用Kettle作业: ' || v_job_path);
-- 示例:通过Java调用
-- 实际实现需要编写Java存储过程或使用外部作业调度器
END;
/
-- 创建调度Job
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'JOB_RUN_KETTLE_STK_INDX',
job_type => 'EXECUTABLE',
job_action => '/home/kettle/kitchen.sh',
number_of_arguments => 5,
enabled => FALSE
);
DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('JOB_RUN_KETTLE_STK_INDX', 1, '-file=/home/kettle/jobs/load_stk_indx.kjb');
DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('JOB_RUN_KETTLE_STK_INDX', 2, '-level=Basic');
DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('JOB_RUN_KETTLE_STK_INDX', 3, '-logfile=/home/kettle/logs/stk_indx.log');
DBMS_SCHEDULER.ENABLE('JOB_RUN_KETTLE_STK_INDX');
END;
/
✅ 方案比较
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| DBMS_SCHEDULER | 功能强大,支持复杂调度,有完善监控 | Oracle 10g+ 可用 | 生产环境推荐 |
| DBMS_JOB | 兼容性好,简单易用 | 功能较简单,旧版本可能不再更新 | 旧版本Oracle或简单任务 |
| 操作系统Cron | 跨数据库通用,调度灵活 | 需要数据库连接信息,安全性考虑 | 多数据库环境或混合调度 |
| Kettle调度 | 可视化配置,有作业流控制 | 依赖Kettle服务,增加系统复杂度 | 已有Kettle调度体系 |
✅ 推荐的生产环境方案
综合方案:DBMS_SCHEDULER + 监控 + 报警
-- 1. 创建增强版存储过程(带日志和异常处理)
CREATE OR REPLACE PROCEDURE PROC_LOAD_STK_INDX_PROD
AS
PRAGMA AUTONOMOUS_TRANSACTION;
v_job_name VARCHAR2(100) := 'STK_INDX_LOAD';
v_log_id NUMBER;
v_start_time TIMESTAMP := SYSTIMESTAMP;
v_row_count NUMBER;
BEGIN
-- 记录开始
INSERT INTO JOB_EXECUTION_LOG (
JOB_NAME, START_TIME, STATUS, PARAMETERS
) VALUES (
v_job_name, v_start_time, 'STARTED',
'FULL_LOAD=Y'
) RETURNING LOG_ID INTO v_log_id;
COMMIT;
-- 执行核心业务逻辑
PROC_LOAD_STK_MKT_QUOT_INDX;
-- 获取处理行数(假设原存储过程有输出参数)
v_row_count := SQL%ROWCOUNT;
-- 记录成功
UPDATE JOB_EXECUTION_LOG
SET END_TIME = SYSTIMESTAMP,
STATUS = 'SUCCESS',
ROW_COUNT = v_row_count,
MESSAGE = '处理完成: ' || v_row_count || ' 行',
DURATION = (SYSTIMESTAMP - v_start_time) * 86400
WHERE LOG_ID = v_log_id;
COMMIT;
-- 可选的报警通知(调用邮件存储过程)
-- SEND_JOB_SUCCESS_ALERT(v_job_name, v_row_count, v_start_time, SYSTIMESTAMP);
EXCEPTION
WHEN OTHERS THEN
-- 记录失败
UPDATE JOB_EXECUTION_LOG
SET END_TIME = SYSTIMESTAMP,
STATUS = 'FAILED',
ERROR_MSG = SQLERRM,
ERROR_CODE = SQLCODE,
DURATION = (SYSTIMESTAMP - v_start_time) * 86400
WHERE LOG_ID = v_log_id;
COMMIT;
-- 发送失败报警
-- SEND_JOB_FAILURE_ALERT(v_job_name, SQLCODE, SQLERRM);
RAISE;
END;
/
-- 2. 创建调度Job
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'JOB_STK_INDX_PROD',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN PROC_LOAD_STK_INDX_PROD; END;',
start_date => TRUNC(SYSDATE) + INTERVAL '2' HOUR, -- 凌晨2点
repeat_interval => 'FREQ=DAILY; BYHOUR=2',
enabled => TRUE,
comments => '生产环境股票指标装载作业'
);
END;
/
-- 3. 创建监控视图
CREATE OR REPLACE VIEW V_JOB_STK_INDX_MONITOR AS
SELECT
JOB_NAME,
LAST_START_DATE,
NEXT_RUN_DATE,
STATE,
RUN_COUNT,
FAILURE_COUNT,
(SELECT MAX(START_TIME) FROM JOB_EXECUTION_LOG WHERE JOB_NAME = J.JOB_NAME) AS LAST_EXEC_TIME,
(SELECT STATUS FROM JOB_EXECUTION_LOG WHERE JOB_NAME = J.JOB_NAME AND START_TIME =
(SELECT MAX(START_TIME) FROM JOB_EXECUTION_LOG WHERE JOB_NAME = J.JOB_NAME)) AS LAST_STATUS
FROM USER_SCHEDULER_JOBS J
WHERE JOB_NAME LIKE '%STK_INDX%';
✅ 最佳实践建议
- 使用DBMS_SCHEDULER:功能最全,Oracle官方推荐
- 添加完整日志:记录开始、结束、行数、耗时
- 设置超时控制:避免长时间运行
- 配置失败重试:可设置重试次数
- 添加监控报警:邮件或短信通知
- 定期清理日志:避免日志表过大
- 测试环境验证:先在测试环境验证调度逻辑
- 文档记录:记录作业目的、时间、负责人
推荐的最小化配置示例:
-- 最简单的生产可用方案
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'JOB_LOAD_STK_INDX_DAILY',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN PROC_LOAD_STK_MKT_QUOT_INDX; END;',
start_date => TRUNC(SYSDATE) + 1 + 1/24, -- 明天凌晨1点
repeat_interval => 'FREQ=DAILY; INTERVAL=1',
enabled => TRUE
);
END;
/
这样配置后,存储过程就会在每天凌晨1点自动执行,实现完全自动化的数据装载。