必须显式写limit在fetch末尾,因bulk collect无默认行数限制,否则全结果集一次性加载至pga引发ora-04030崩溃;limit值需据单行大小调整,纯字段建议500,含lob须≤100,rac下pga共享更需谨慎。

为什么LIMIT必须显式写在FETCH末尾
BULK COLLECT本身不带默认行数限制,FETCH c_emp BULK COLLECT INTO l_tab 这种写法会把整个结果集一次性加载进PGA内存。不是慢,是直接崩——ORA-04030 出现前往往没任何预警。LIMIT只能出现在FETCH语句末尾,不能塞进SELECT或OPEN里,否则语法报错或被忽略。
LIMIT值怎么选:100–1000不是拍脑袋定的
单行数据越大,LIMIT就得越小。实测经验:
- 纯数值/字符串字段,每行平均
1–2KB,LIMIT 500是19c OLTP场景下吞吐与内存占用的平衡点 - 含
CLOB或BLOB字段时,必须压到100以下;哪怕只有一列CLOB,单行可能占几十KB,LIMIT 500就轻松吃掉几百MB PGA - RAC环境下别信“我这台服务器内存大”,PGA是共享资源,一个游标刷爆会影响其他所有会话
退出循环别用%NOTFOUND,要用COUNT判断
%NOTFOUND在BULK COLLECT后不可靠。比如最后一批只取到1行,%NOTFOUND仍是FALSE;再FETCH一次才变TRUE——这会导致多一次空读,浪费IO且可能拖慢整体节奏。
正确做法是每次FETCH后立刻检查集合COUNT:
FETCH c_emp BULK COLLECT INTO l_tab LIMIT 500; EXIT WHEN l_tab.COUNT = 0;
注意:l_tab.COUNT = 0 才代表真的没数据了,不是%NOTFOUND。
FORALL和COMMIT也得配合LIMIT一起调
BULK COLLECT只是“批量取”,不配FORALL就是伪批量——上下文切换照旧频繁。而COMMIT也不能每批都来一次:
- 每批都
COMMIT会引发I/O过载,尤其高并发时 - 建议加计数器,比如累计处理满
1000行再COMMIT,而不是每LIMIT就提交 - 集合类型必须是
TYPE IS TABLE OF ...或INDEX BY PLS_INTEGER,否则FORALL可能报ORA-06502
真正容易被忽略的是:LIMIT不是孤立参数,它和COUNT判断、FORALL范围、COMMIT节奏是一套动作,漏掉任何一个环节,批量就退化成“看起来批量、实际逐行”。











