必须用 DUP_VAL_ON_INDEX 而不是 SQLCODE = -1,因其语义明确、编译期绑定、开销低、不受语言环境和事务上下文干扰;SQLCODE = -1 易误判且不安全。

直接用 DUP_VAL_ON_INDEX 异常名捕获,别去解析错误码字符串
为什么必须用 DUP_VAL_ON_INDEX 而不是 SQLCODE = -1
Oracle 预定义了 DUP_VAL_ON_INDEX 异常,它专为 ORA-00001 触发,语义清晰、稳定可靠。而手动判断 SQLCODE = -1 容易误伤:某些内部操作(如某些隐式类型转换失败)也可能返回 -1,但实际不是唯一冲突。更关键的是,DUP_VAL_ON_INDEX 在 PL/SQL 编译期就绑定到该错误,执行时开销更低。
常见错误现象:写成 WHEN OTHERS THEN IF SQLCODE = -1 THEN ...,结果掩盖了其他真正需要关注的 -1 错误,或漏掉因事务隔离级别导致的延迟报错场景。
- 预定义异常名是 Oracle 官方保障的映射,比硬编码数字更安全
- 不能依赖
SQLERRM包含 "unique" 字样做字符串匹配——不同语言环境(如中文数据库)下提示文本会变 - 若在自治事务中插入,
DUP_VAL_ON_INDEX仍能准确捕获,而SQLCODE判断可能受事务上下文干扰
INSERT ... SELECT 场景下无法触发 DUP_VAL_ON_INDEX 的真实原因
当使用 INSERT INTO t SELECT ... FROM u 批量插入时,哪怕其中某行违反唯一约束,Oracle 默认不抛出 DUP_VAL_ON_INDEX,而是直接报 ORA-00001 并中断整个语句——因为这是 SQL 层面的批量操作,PL/SQL 异常处理器只捕获语句级异常,而该语句本身已“失败退出”,不会进入异常块。
解决办法只有两个方向:
- 改用游标逐行
INSERT,每行独立触发异常,可精确控制重试或跳过逻辑 - 提前用
MERGE替代INSERT,把冲突逻辑显式写进 SQL,避免异常路径 - 对极大数据量,可用
LOG ERRORS INTO子句将冲突行导出到错误日志表,后续再统一处理
并发插入时仅靠异常捕获不够,必须加 SELECT ... FOR UPDATE
单纯在 EXCEPTION 块里捕获 DUP_VAL_ON_INDEX 并重试,大概率陷入“检查-插入”竞态:两个会话同时查不到数据 → 同时尝试插入 → 其中一个必然失败 → 重试又撞上另一个刚插入的值 → 再失败。这不是代码问题,是逻辑漏洞。
正确做法是在插入前锁定潜在冲突行:
- 先执行
SELECT 1 FROM t WHERE key_col = :val FOR UPDATE NOWAIT - 如果查到记录,说明已存在,按业务规则决定是跳过、更新还是报错
- 如果没查到,再执行
INSERT;此时即使并发插入,也只会有一个成功,另一个被锁阻塞后报ORA-00054,而非ORA-00001 - 注意
NOWAIT避免无限等待,配合重试逻辑更可控
自定义冲突处理逻辑要避开 PRAGMA EXCEPTION_INIT 的典型误用
有人会写 PRAGMA EXCEPTION_INIT(dup_err, -1) 来“重新命名”异常,这完全没必要,且容易出错:DUP_VAL_ON_INDEX 已是标准名,无需再映射。真正需要 PRAGMA EXCEPTION_INIT 的,是那些没有预定义名的非标准错误(比如自定义应用错误码 ORA-20001)。
更隐蔽的问题是:在存储过程中嵌套调用另一个可能抛 ORA-00001 的过程时,若外层没声明 DUP_VAL_ON_INDEX,异常会直接向上冒泡,而不是被内层的 PRAGMA 捕获——PRAGMA 只作用于当前 PL/SQL 块声明部分。
所以,最简实践就是:每个可能插入的 PL/SQL 块,都直接写 WHEN DUP_VAL_ON_INDEX THEN ...,不绕弯。


















