forall 是 oracle 批量 dml 的底层优化机制,非简单加速版 for 循环;须确保集合 count 严格一致、批量大小 500~2000、关闭 autocommit、正确处理 save exceptions 错误索引,并禁用表达式与动态 sql。
forall 不是“更快的 for 循环”,它本质是 oracle 对批量 dml 的底层 sql 引擎优化机制;用错集合对齐、盲目调大批量或混用动态赋值,性能反而比逐行还差。
FORALL 更新必须保证所有绑定集合 COUNT 完全一致
Oracle 不校验逻辑对齐,只按索引位置硬绑定。一旦 a_arr.COUNT ≠ b_arr.COUNT,立刻报 ORA-06512。常见错误包括:
- 一个集合用
EXTEND动态追加,另一个用固定下标(如b_arr(5) := 'xxx')导致稀疏空位 - FETCH BULK COLLECT 时 LIMIT 超出剩余行数,但后续未检查
l_ids.COUNT就直接进 FORALL - 用了
INDICES OF idx_list,但其他集合没做对应映射,造成错位
实操建议:每次 FETCH 后立即加 DBMS_OUTPUT.PUT_LINE('ids:'||l_ids.COUNT||' names:'||l_names.COUNT);填充集合统一用同一循环,例如 FOR i IN 1..n LOOP a_arr.EXTEND; a_arr(i):=...; b_arr.EXTEND; b_arr(i):=...; END LOOP;
批量大小控制在 500~2000 是性能甜点区
设太大(如 LIMIT 10000)会导致 PGA 内存暴涨、临时段写入频繁、游标失效,尤其当目标表有 3+ 索引或触发器时,优势迅速归零。19c 隐式分片优化依赖可控的单次绑定量。
- OLTP 场景推荐
LIMIT 500~2000,实测吞吐最稳 - 务必关闭
AUTOCOMMIT,否则每次 FORALL 后自动提交,退化成单条执行 - 每批执行后显式
COMMIT,避免长事务锁表和回滚段压力
SAVE EXCEPTIONS 下 ERROR_INDEX 不是源数组下标
启用 SAVE EXCEPTIONS 后,SQL%BULK_EXCEPTIONS(i).ERROR_INDEX 返回的是 FORALL 内部执行序号(从 1 开始),不是你原始集合的物理下标。直接拿它去查 id_arr 会取错记录。
- 正确写法:
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全部不支持
实操建议:预处理集合,例如先循环生成 name_upper(i) := UPPER(name_arr(i)),再在 FORALL 中直引 name_upper(i);复杂逻辑拆到 BULK COLLECT 前或后处理。
真正难的不是写 FORALL,而是确保所有集合在每一行都严格对齐、每一批次都可控可查、每一个错误都能准确定位到原始业务主键——这些细节不靠调试,只靠初始化时就写死校验逻辑。











