forall 比普通 for 循环快,因其将多次单条 dml 压缩为一次批量操作,减少 pl/sql 与 sql 引擎间上下文切换;但仅适用于 dml + 集合场景,需配合 bulk collect 填充集合,且集合必须为 index by pls_integer 类型。
forall 为什么比普通 for 循环快?
因为 FORALL 把多次单条 SQL 执行,压成一次批量 DML 操作发给 SQL 引擎,省去了 PL/SQL 和 SQL 引擎之间反复切换的开销(context switch)。普通 FOR 循环每执行一次 INSERT 或 UPDATE,都要进出 SQL 引擎一次;FORALL 只进一次,批量处理整个集合。
但这个优势只在「DML + 集合」场景下成立——它本身不执行查询,也不自动填充数据,必须配合 BULK COLLECT 或手动构造集合。
- 没用
BULK COLLECT填充集合,FORALL就是空转,甚至报ORA-22160: element at index [N] does not exist -
FORALL不做语法检查:如果集合里某条记录违反约束(比如主键冲突),整个批次会失败,除非加SAVE EXCEPTIONS - 不能在
FORALL中调用函数或写复杂表达式,绑定变量只能是集合元素,如emp_ids(i)、salaries(i)
BULK COLLECT 怎么避免内存爆掉?
BULK COLLECT 一次性把结果集全捞进内存,查几百万行就可能 OOM 或触发 PGA 自动收缩,导致性能陡降。关键不是“要不要用”,而是“怎么控量”。
必须显式限制每次取多少行,靠 LIMIT 子句。不写 LIMIT,等于默认无上限——Oracle 会尽可能多拿,直到内存撑不住。
- 典型安全值是
LIMIT 1000到LIMIT 10000,具体看单行数据大小和可用 PGA - 不要用
COUNT(*)先查总数再分配集合,既多一次全表扫描,又可能因并发修改导致计数不准 - 循环中每次
BULK COLLECT INTO后,立刻检查集合.COUNT是否为 0,作为退出条件,否则可能无限循环
FETCH emp_cur BULK COLLECT INTO emp_ids, emp_names LIMIT 5000; EXIT WHEN emp_ids.COUNT = 0;
FORALL + BULK COLLECT 组合时最容易漏的三件事
这不是两个命令拼起来就能跑通的组合技,中间有三处隐含依赖关系,漏一个就会报错或逻辑错。
- 集合类型必须是
INDEX BY PLS_INTEGER(即关联数组),不能是嵌套表或可变数组——FORALL只认这种下标连续、从 1 开始的结构 -
FORALL i IN INDICES OF emp_ids比FORALL i IN 1 .. emp_ids.COUNT更安全:前者跳过被DELETE过的稀疏索引,后者遇到空洞直接报错 - 如果用了
SAVE EXCEPTIONS,必须配SQL%BULK_EXCEPTIONS.COUNT和循环读取异常信息,否则错误被吞掉,你以为成功了其实部分失败
什么时候不该硬上 FORALL/BULK COLLECT?
小数据量(比如稳定
PL/SQL 的批量优化本质是「用内存换 IO 和上下文切换」,但内存不是免费的,调试成本也会上升——出错时你看到的是第 3827 行失败,而不是清晰的循环变量值。
- 单次处理
- 需要对每行做独立判断再决定是否 DML(比如
IF salary > threshold THEN UPDATE...),先BULK COLLECT再逐个判断,不如边查边处理 - 目标表有大量触发器或外键级联操作,批量 DML 可能放大锁竞争,反而拖慢整体吞吐
真正要盯住的,是那个反复执行、且每次只改一两行的循环——那里才是批量优化的甜点区。










