forall 是 oracle 对批量 dml 的 sql 引擎级优化,非简单加速的 for 循环;需确保集合长度严格相等、批量大小 500~2000、关闭 autocommit、正确处理 save exceptions 索引映射,并预处理表达式。

FORALL 不是“更快的 FOR 循环”,它本质是 Oracle 对批量 DML 的 SQL 引擎级优化;用错集合对齐、盲目放大批量或混用表达式,性能反而比逐行还差。
FORALL 绑定的多个集合 COUNT 必须严格相等
Oracle 不校验逻辑对齐,只按索引位置硬绑定。一旦 a_arr.COUNT ≠ b_arr.COUNT,立刻报 ORA-06512(常伴 ORA-06502 或 ORA-01408)。常见错误包括:
- 一个集合用
EXTEND动态追加,另一个用固定下标赋值(如b_arr(5) := 'xxx'),导致稀疏空位 -
BULK COLLECT ... LIMIT 1000后未检查l_ids.COUNT就直接进FORALL - 用了
INDICES OF idx_list,但其他集合没做对应映射,造成错位
实操建议:填充统一用同一循环,例如
FOR i IN 1..n LOOP a_arr.EXTEND; a_arr(i) := val_a; b_arr.EXTEND; b_arr(i) := val_b; END LOOP;
调试必加:DBMS_OUTPUT.PUT_LINE('ids:'||l_ids.COUNT||' names:'||l_names.COUNT);
批量大小控制在 500~2000 是性能甜点区
设太大(如 LIMIT 10000)会导致 PGA 内存暴涨、临时段写入频繁、游标失效,尤其当目标表有 3+ 索引或触发器时,优势迅速归零。19c 的隐式分片优化依赖可控的单次绑定量。
- OLTP 场景推荐
LIMIT 500~2000,实测吞吐最稳 - 超 5000 易触发回滚段压力和锁竞争,不如拆成多个小事务
- 务必关闭
AUTOCOMMIT,否则每次FORALL后自动提交,退化成单条执行
每批执行后显式 COMMIT,避免长事务锁表。
SAVE EXCEPTIONS 下 ERROR_INDEX 不是原始数组下标
启用 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只返回最后一条
FORALL 不支持表达式、函数或条件分支
FORALL 只接受静态 SQL,所有绑定变量必须是集合元素直引。以下全是非法写法:
-
FORALL i IN 1..arr.COUNT UPDATE t SET name = UPPER(arr2(i)) WHERE id = arr(i)→ 报ORA-06550 -
CASE WHEN ... THEN ...、子查询、@dblink全部不支持
正确做法:预处理集合,例如 arr2_upper(i) := UPPER(arr2(i)),再进 FORALL。
最容易被忽略的点是:FORALL 的性能收益高度依赖上下文切换节省,而这个收益会被不一致的集合长度、过大的批量、未关闭的 AUTOCOMMIT 或错误的异常索引映射彻底抵消——它不是“开了就快”,而是“对了才快”。










