不加LIMIT的BULK COLLECT会一次性将全表数据加载至PGA,极易触发ORA-04030导致OOM崩溃;必须显式指定LIMIT(建议1000~5000),并每次循环前清空集合、检查SQL%BULK_ROWCOUNT。
BULK COLLECT 不加 LIMIT 会直接 OOM
全表 bulk collect 百万行数据,pga 内存瞬间打满,触发 ora-04030 —— 这不是慢,是直接崩。oracle 不会自动分页,它真的一次性往内存里“灌”。
- 初值建议设
LIMIT 1000~5000,再根据实际 PGA 大小(查V$PGASTAT)逐步调高 - 显式声明集合类型必须带
LIMIT子句,比如TYPE t_tab IS TABLE OF emp%ROWTYPE INDEX BY PLS_INTEGER;后不能直接BULK COLLECT INTO t_tab,得用INTO l_tab LIMIT n - 若用隐式游标(
SELECT ... BULK COLLECT INTO),LIMIT必须写在INTO后,不能放在WHERE后或当子句用
FORALL 更新时 SQL%BULK_ROWCOUNT 返回 0 不报错
FORALL 执行后,部分元素可能因 WHERE 条件不匹配而未更新,但 Oracle 默认沉默——SQL%ROWCOUNT 只返回总影响行数,SQL%BULK_ROWCOUNT(i) 才告诉你第 i 个元素实际改了几行。漏查这个,逻辑就残了。
- 每次
FORALL后必须遍历SQL%BULK_ROWCOUNT,检查是否为 0;例如:IF SQL%BULK_ROWCOUNT(i) = 0 THEN ... - 别依赖
SQL%NOTFOUND或SQL%FOUND,它们只反映最后一次 DML,对批量无效 - 想捕获具体哪几条失败?加
SAVE EXCEPTIONS,但注意:它会让 Oracle 维护异常堆栈,性能略降,且SQL%BULK_EXCEPTIONS.COUNT才是真实失败数
集合变量重复赋值前没清空,旧数据还在
PL/SQL 集合不是“覆盖写”,而是“追加写”。如果上一轮 BULK COLLECT 拿到 1000 行,下一轮只拿到 200 行,但没清空集合,那 l_tab.COUNT 还是 1000,后 800 个元素是 NULL —— FORALL 仍会尝试执行,可能引发 ORA-06502 或逻辑错乱。
- 安全做法:每次循环开始前加
l_tab.DELETE;(对嵌套表/关联数组都有效) - 或显式重置:
l_tab := t_tab();,但注意类型要完全一致,否则报PLS-00382 - 别用
l_tab.TRIM(l_tab.COUNT)清空,那是删末尾元素,不是清空整个集合
BULK COLLECT + FORALL 组合中,绑定变量必须是集合元素引用
FORALL i IN 1..l_ids.COUNT 里的 l_ids(i) 是合法绑定;但 l_ids(i) + 1、TO_NUMBER(l_ids(i))、甚至 l_ids(i) 加括号外的任何修饰,都会让 Oracle 报 PLS-00430:绑定变量必须是简单集合索引表达式。
- 所有计算、类型转换、函数调用,必须在
BULK COLLECT前完成,塞进集合里;例如先SELECT employee_id * 100 BULK COLLECT INTO l_ids,再FORALL - 多列结果不能直接用
%ROWTYPE数组接收,必须显式声明集合类型,如TYPE t_emp_tab IS TABLE OF emp%ROWTYPE; -
FORALL不支持IF、CONTINUE等控制语句,分支逻辑只能前置过滤:该WHERE的在查询里写好,该CASE的在SELECT列表里算好
真正卡住人的,从来不是语法写不对,而是集合没清、LIMIT 忘设、SQL%BULK_ROWCOUNT 没查——这些地方一松懈,程序要么内存炸,要么数据丢,还查不出错在哪。


















