FORALL语句语法严格限制仅允许紧随其后出现单条DML语句,禁止嵌套BEGIN EXCEPTION END等PL/SQL块,否则在解析阶段即报PLS-00103错误;正确容错方式是使用SAVE EXCEPTIONS子句配合自定义异常、PRAGMA EXCEPTION_INIT(-24381)及SQL%BULK_EXCEPTIONS遍历定位错误行。
FORALL 里不能写 BEGIN EXCEPTION END,否则直接报 PLS-00103;必须用 SAVE EXCEPTIONS + SQL%BULK_EXCEPTIONS 才能捕获行级错误。
FORALL 语句语法只允许紧接一条静态 DML(INSERT/UPDATE/DELETE),不接受任何 PL/SQL 块嵌套。一旦出现 BEGIN、EXCEPTION 或 IF,解析器立刻报 PLS-00103——它根本没走到执行阶段,纯属语法硬限制。
常见错误写法:
FORALL i IN 1..arr.COUNTBEGININSERT INTO t VALUES (arr(i));EXCEPTION WHEN DUP_VAL_ON_INDEX THEN NULL;END;
这在语法校验阶段就失败,和数据无关。
FOR 循环,但性能损失显著SAVE EXCEPTIONS
SQL%BULK_EXCEPTIONS(i).ERROR_INDEX 返回的是 FORALL 内部执行序号(从 1 开始),不是你源集合的物理下标。直接用它查 id_arr 会取错元素。
正确做法分两种情况:
INDICES OF:直接映射 → failed_id := id_arr(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
INDICES OF idx_list:需两级映射 → orig_idx := idx_list(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX); failed_id := id_arr(orig_idx);
同时别漏负号:SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE) 才能拿到真实错误信息;只写 SQLERRM 永远返回最后一条错误。
光写 SAVE EXCEPTIONS 不够,必须配对三要素:自定义异常声明、EXCEPTION 块捕获、遍历 SQL%BULK_EXCEPTIONS。
关键代码骨架:
DECLAREbulk_errors EXCEPTION;PRAGMA EXCEPTION_INIT(bulk_errors, -24381); -- 必须显式绑定 Oracle 错误码l_ids SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST(1,2,3);l_valsSYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST('a','b','c');BEGINFORALL i IN 1..l_ids.COUNT SAVE EXCEPTIONSINSERT INTO t_test (id, val) VALUES (l_ids(i), l_vals(i));EXCEPTION WHEN bulk_errors THEN FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP DBMS_OUTPUT.PUT_LINE( 'Failed at position ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX || ', error code ' || SQL%BULK_EXCEPTIONS(i).ERROR_CODE || ', message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE) ); END LOOP; END;
PRAGMA EXCEPTION_INIT,WHEN bulk_errors 永远不会触发SQL%BULK_EXCEPTIONS.COUNT 就进循环,可能报 ORA-06533(下标越界)COMMIT 或 ROLLBACK,成功行状态不确定批量异常处理最常栽在“以为错了就全回滚”——其实 SAVE EXCEPTIONS 下成功行默认已提交,失败行被跳过,事务处于半开状态。
ERROR_CODE 可能是 ORA-04091(表正在变动),而非你预期的主键冲突DBMS_ERRLOG 创建错误日志表虽可持久化,但额外 I/O 开销,小批量不如内存处理快