forall 比逐行 insert 快,因其将 n 次单条 dml 合并为一次批量操作,避免 pl/sql 与 sql 引擎间反复切换;底层一次性传递绑定数组,省去重复解析执行。
forall 为什么比逐行 insert 快得多
因为 FORALL 把多次单条 SQL 执行,合并成一次批量 DML 操作,绕过了 PL/SQL 引擎和 SQL 引擎之间反复切换的开销。Oracle 在底层会把绑定数组一次性传给 SQL 引擎,相当于执行一条带集合参数的 INSERT,而不是 N 次独立解析、执行、提交。
常见错误现象:ORA-06502: PL/SQL: numeric or value error —— 多半是集合元素为空或类型不匹配;ORA-01403: no data found 在 FORALL ... SAVE EXCEPTIONS 里没捕获,直接报错中断。
实操建议:
- 必须用
BULK COLLECT获取的数据(或你自己构造的INDEX BY/ 嵌套表)才能传给FORALL,不能传普通标量变量 -
FORALL i IN 1..emp_tab.COUNT中的下标范围必须连续,否则加INDICES OF emp_tab或VALUES OF idx_tab显式指定有效索引 - 避免在
FORALL内部调用函数或计算表达式——所有值应在循环前准备好,否则失去批量意义
BULK COLLECT 的内存与分页陷阱
BULK COLLECT 一次性把结果集全读进内存,数据量大时容易撑爆 PGA,触发 ORA-04030: out of process memory。它不是“自动分页”,而是“全量加载”。
使用场景:适合几千到几万行的中等批量;超过十万行,必须配合 LIMIT 分批。
实操建议:
- 永远显式写
BULK COLLECT INTO emp_tab LIMIT 1000,别依赖默认行为(默认无上限) - 用
emp_tab.COUNT = 0判断是否取完,而不是%NOTFOUND(游标属性在 BULK 后不准确) - 集合类型优先选
TABLE OF ...(嵌套表),别用VARRAY(长度固定易溢出)或ASSOCIATIVE ARRAY(某些 DML 场景不支持绑定)
FORALL + SAVE EXCEPTIONS 怎么真正捕获错误
只写 SAVE EXCEPTIONS 不够,不查 SQL%BULK_EXCEPTIONS 就等于没捕获。而且异常记录的错误编号是 SQLERRM(-sqlerrm_code),不是直接 SQLERRM。
常见错误现象:循环中途报错,但没看到具体哪条数据出问题;或者 SQL%BULK_EXCEPTIONS.COUNT = 0 却仍报错——说明根本没进 SAVE EXCEPTIONS 分支(比如语法错在 FORALL 外)。
实操建议:
- 必须紧跟
FORALL后立刻用EXCEPTION WHEN OTHERS THEN包裹,并在其中检查SQL%BULK_EXCEPTIONS.COUNT - 遍历异常时,用
SQL%BULK_EXCEPTIONS(i).ERROR_INDEX对应回原始集合下标,别直接当数组索引用 - 错误信息要拼上
SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE),负号不能漏,否则返回空字符串
什么时候不该用 BULK COLLECT + FORALL
当你要在每行处理中做复杂逻辑判断、条件跳过、动态 SQL 拼接,或需要实时反馈中间状态(比如日志打点、异步通知),强行塞进批量结构反而更难维护、更难调试。
性能影响:如果只是 10 行以内,批量开销可能比逐行还高;如果目标表有大量触发器、外键约束、审计策略,FORALL 的优势会被抵消,甚至更慢。
实操建议:
- 先用
DBMS_PROFILER或DBMS_HPROF实测,别凭经验假设“批量一定快” - 有
BEFORE/AFTER EACH ROW触发器时,FORALL仍会逐行触发——它不跳过触发器逻辑 - 涉及序列
NEXTVAL时,不能直接在FORALL里写seq.NEXTVAL,得提前用SELECT seq.NEXTVAL BULK COLLECT INTO准备好值
最常被忽略的一点:BULK COLLECT 不会自动关闭游标,FORALL 不会自动提交。忘了 CLOSE 或 COMMIT,轻则锁表,重则事务悬停。











