如何让Oracle PL/SQL任务失败后自动重试

作者:袖梨 2026-08-24

PL/SQL 无法捕获连接中断错误,因为 ORA-03114、ORA-03113 发生在 SQL*Net 网络层,语句未送达数据库或响应丢失,PL/SQL 引擎已失上下文,不进入 EXCEPTION 分支;重试必须由客户端或应用层实现。

PL/SQL 本身无法实现“自动重试”——连接中断、会话失效类错误根本进不了 EXCEPTION 块,重试逻辑必须由客户端或应用层控制。

为什么 PL/SQL 的 WHEN OTHERS 捕获不到连接失败?

ORA-03114(not connected to ORACLE)、ORA-03113(communication channel failure)等错误发生在 SQL*Net 网络层,语句甚至没发到数据库,或响应丢失,PL/SQL 引擎已失去执行上下文,直接报错退出。此时 EXCEPTION 分支完全不触发,WHEN OTHERS 形同虚设。

常见误判场景:

  1. 在存储过程中写 WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20001, 'retry later');,但连接断了根本不会执行这行
  2. DBMS_SCHEDULER 调用一个带事务的存储过程,中间网络抖动导致作业状态变成 STOPPED,但日志里看不到任何异常处理痕迹
  3. 以为 SQLCODE 能拿到 -3113 就能做判断,实际 SQLCODE 在连接中断时根本不可用(变量未初始化或返回 0)

真正可行的重试策略在哪做?

重试动作必须落在调用 PL/SQL 的外部环节,且需区分场景:

  1. SQL*Plus 批处理脚本:用 shell 或 bat 控制循环 + 退出码判断。例如 Linux 下:until sqlplus user/pass@db @job.sql; do sleep 10; done,配合 EXIT WHEN SQLCODE != 0; 在脚本末尾显式退出
  2. Java 应用:捕获 SQLException,检查 getErrorCode() 是否为 3113/3114,手动重建 Connection 并重试逻辑(注意:Oracle JDBC 驱动默认不支持 auto-reconnect,autoReconnect=true 参数无效)
  3. DBMS_SCHEDULER 作业:不能靠 PL/SQL 自身重试,但可配置 MAX_FAILURESRESTARTABLE 属性,再结合 DBMS_SCHEDULER.ADD_EVENT_RULE 监听 job_failed 事件,触发另一个作业做补偿
  4. PL/SQL Developer / SQL Developer:无内置重试机制,只能靠“执行前验证连接”(Tools → Preferences → Connection → Check connection),失败时弹窗提示,由人工点重试

DBMS_SCHEDULER 中怎么模拟“失败后重试”?

虽然不能让单个作业内部循环重试,但可以组合调度能力逼近效果:

  1. 创建主作业调用你的存储过程,设置 max_failures => 3restartable => TRUE
  2. 定义一个事件规则:DBMS_SCHEDULER.ADD_EVENT_RULE 监听 'oracle.scheduler.job_failed',条件是 event_type = 'JOB_FAILED'job_name = 'MY_MAIN_JOB'
  3. 该规则触发一个“重试作业”,它先等待几秒(DBMS_LOCK.SLEEP(5)),再调用同一存储过程;可限制最多触发 2 次,避免无限循环
  4. 注意:所有重试作业都得单独提交事务,不能依赖原作业的事务上下文

示例关键参数:job_action => 'BEGIN my_proc; END;',不是 my_proc(后者不支持参数,且无法捕获执行结果)

最容易被忽略的细节

很多人花时间在 PL/SQL 里加 IF SQLCODE IN (-3113, -3114) THEN ...,却忘了——这个判断只在错误“被抛出并进入 EXCEPTION”时才有效;而连接中断时,SQLCODE 根本没机会被赋值,整块代码已被跳过。真正的防线不在存储过程体里,而在连接保活(如 SQL Developer 的 Keep Alive)、客户端超时设置、连接池 validation-query 配置,以及作业失败后的事件驱动补偿链路上。

相关文章

精彩推荐