未捕获异常会导致PL/SQL块中止并向上抛出,若全程无EXCEPTION处理,则事务悬空、DML可能意外提交或锁资源;必须用自治事务记录日志、显式ROLLBACK并RAISE重抛。

未捕获的异常不会导致“静默回滚”,而是会中止当前 PL/SQL 块并向上抛出异常 —— 但如果你没写 EXCEPTION 块,又没在调用链外层处理它,事务就真的“静默失败”了:既没提交,也没显式回滚,还可能被客户端自动提交或锁住资源。
为什么没写 EXCEPTION 就像没系安全带
PL/SQL 中,只要异常没被当前块的 EXCEPTION 捕获,就会立即终止执行、跳过后续语句,并把异常传给上一层(调用者)。如果整个调用链都没人接住它:
- 在 SQL*Plus 或某些 JDBC 驱动里,连接断开时会隐式
COMMIT—— 前面已执行的 DML 就“意外落库”了 - 在存储过程中用了
FOR UPDATE却中途崩溃,锁一直挂着,直到会话超时或手动 kill - DBMS_JOB 调度的任务若没异常处理,
FAILURES字段自增,NEXT_DATE停滞,但日志里什么都没有
必须写的 EXCEPTION 块长什么样
不是“加个 WHEN OTHERS THEN NULL”就完事。真正防静默的关键是三件事:记录、回滚、重抛。
- 所有 DML 操作后,若没显式
COMMIT,就必须在EXCEPTION块里写ROLLBACK—— 否则事务状态悬空 - 用
DBMS_OUTPUT.PUT_LINE不够,得写入表(如t_log),且必须通过自治事务,否则日志也随主事务一起丢 - 最后一定要
RAISE(或RAISE_APPLICATION_ERROR),让调用方知道出错了;只ROLLBACK不重抛,上层还以为成功了
示例:
EXCEPTION
WHEN NO_DATA_FOUND THEN
ROLLBACK;
p_log_error('查询不到目标记录: ' || SQLERRM);
RAISE;
WHEN OTHERS THEN
ROLLBACK;
p_log_error('未知错误: ' || SQLERRM || ' at line ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
RAISE;
END;
自治事务日志为什么不能省
很多人在 EXCEPTION 块里直接写 INSERT INTO t_log ...; COMMIT;,结果日志根本没留下 —— 因为这条 INSERT 仍属于主事务,ROLLBACK 时一并撤销。
- 必须用
PRAGMA AUTONOMOUS_TRANSACTION把日志过程完全隔离 - 自治过程内
COMMIT是独立的,不受外层事务影响 - 即使主过程因权限错、表不存在等严重错误根本进不了
EXCEPTION块,自治日志调用本身失败也会触发自己的EXCEPTION,形成兜底
最容易被忽略的两个上下文陷阱
事务行为不取决于你写了什么,而取决于谁在运行、在哪运行、怎么运行:
-
DBMS_JOB任务以创建者身份运行,不继承角色权限 —— 存储过程中访问的表必须显式GRANT,否则直接报ORA-00942,且不会进你的EXCEPTION块(编译期错) - 应用层用
@Transactional包裹了对存储过程的调用,那 PL/SQL 内部的COMMIT会破坏 Spring 的事务管理,导致部分提交、部分回滚,极难排查
复杂点从来不在语法,而在事务边界和执行环境是否对齐 —— 写完 EXCEPTION 块只是开始,得去查 DBA_JOBS、V$SESSION、V$LOCK,才能确认它真正在按你想的方式收尾。











