forall是oracle批量dml的底层优化机制,非简单循环替代品;必须确保所有绑定集合count完全一致,否则触发ora-06512等错误;慎用indices of、save exceptions需用error_index反查原索引;批量大小宜控在500~2000;不支持条件分支、表达式计算或跨库操作。
forall 不是“更快的 for 循环”,它是 oracle 对批量 dml 的底层优化机制;用错语法、忽略集合一致性或盲目调大批量,反而比普通循环还慢。
FORALL 绑定的多个集合必须 COUNT 完全一致
ORA-06512 错误绝大多数源于 a_arr.COUNT ≠ b_arr.COUNT。FORALL 不校验逻辑对齐,只按索引位置硬绑定——哪怕 b_arr(5) 是 NULL 或未赋值,Oracle 仍会尝试传入空值或触发 ORA-01408(重复索引)等连锁错误。
- 填充时统一用同一
FOR i IN 1..n循环,避免一个用EXTEND动态追加、另一个用固定下标赋值 - 调试必加:
DBMS_OUTPUT.PUT_LINE('a:'||a_arr.COUNT||' b:'||b_arr.COUNT);,别等报错再查 - 慎用
INDICES OF:它跳过稀疏空位,但其他集合不会自动对齐,极易错位
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单独调用只返回最后一条
BULK COLLECT + FORALL 的 LIMIT 别设太大
很多人把 BULK COLLECT INTO ... LIMIT 10000 当成“越大越快”,结果 PGA 暴涨、GC 频繁,实际吞吐反而下降。Oracle 12c+ 的隐式分片优化依赖可控的单次绑定量。
- OLTP 场景推荐
LIMIT 500~2000,实测为性能甜点区 - 超 5000 易触发临时段写入、游标失效,尤其当目标表有 3+ 索引或触发器时,优势快速归零
- 务必关闭 AUTOCOMMIT;否则每次 FORALL 后自动提交,等于退化成单条插入
FORALL 不支持条件分支和表达式计算
FORALL 只接受静态 SQL 语句,所有绑定变量必须是集合元素直引,不能带函数、CASE、子查询或跨库 DBLink。
- 错误写法:
FORALL i IN 1..arr.COUNT INSERT INTO t VALUES (arr(i), UPPER(arr2(i)))→ ORA-06550 - 正确做法:预处理集合,
arr2_upper(i) := UPPER(arr2(i)),再进 FORALL - 不支持
IF分支,需要不同逻辑的 DML 必须拆成多个 FORALL 块
最常被忽略的一点:FORALL 的性能收益高度依赖目标表结构。索引越多、触发器越重、约束越严,批量优势衰减越快——这时候与其死磕 FORALL,不如先禁用索引、关闭触发器,插完再重建。











