Oracle游标本身不直接溢出内存,但未正确管理的显式游标和无LIMIT的BULK COLLECT会快速耗尽PGA,引发OOM或ORA-01000;必须在正常流程末尾和EXCEPTION块中显式CLOSE游标,并对BULK COLLECT严格限制每次FETCH行数且及时清空集合。
oracle游标本身不直接“溢出内存”,但未正确管理的显式游标 + 不受控的 bulk collect 会快速耗尽 pga,触发 oom 或 ora-01000。
显式游标不 CLOSE 就真不释放 PGA
PL/SQL 中 CURSOR c IS SELECT ... 只是声明,OPEN c 才真正分配服务器端游标句柄和 PGA 内存。漏掉 CLOSE c,这块内存就不会归还——哪怕 PL/SQL 块执行结束,只要游标被传递出作用域(比如赋给包级变量)、或发生异常跳过正常退出路径,它就一直挂着。
- 常见错误现象:
v$open_cursor里反复看到同一游标名、opened cursors current持续上涨、total PGA used mem缓慢爬升 - 必须在正常流程末尾和
EXCEPTION块中都写IF c%ISOPEN THEN CLOSE c; END IF;,不能只依赖块结束隐式关闭 - 尤其注意:JDBC 层的
ResultSet.close()和 PL/SQL 层的CLOSE是两回事,后者关的是 Oracle 服务端资源,前者只是客户端对象
BULK COLLECT 不加 LIMIT = 主动申请 OOM
BULK COLLECT INTO 不是“懒加载”,而是把结果集整批拷进 PL/SQL 表(即内存中的集合类型),大小由行数 × 单行平均字节决定。没 LIMIT,等于让 Oracle 把几百万行全塞进 PGA。
- 错误写法:
FETCH c BULK COLLECT INTO t_tab;(无 LIMIT) - 正确写法:
FETCH c BULK COLLECT INTO t_tab LIMIT 1000;,数值按单行约 2KB 估算,100–5000 是较安全区间 - 每次
FETCH ... LIMIT n后,必须立刻处理数据,并调用t_tab.DELETE;清空集合引用,否则上一批数据仍驻留内存,GC 不回收 - 若后续用
FORALL INSERT回写,同样要分批,避免 PGA 突增
FOR LOOP 游标看似省事,其实暗藏风险
FOR r IN (SELECT ...) LOOP 底层仍是打开游标 + 隐式 FETCH,它不缓存全部结果,这点比无 LIMIT 的 BULK COLLECT 安全,但有两个硬伤:
- 循环体里做耗时操作(如远程调用、写文件),游标会长时间保持打开,延长服务器端资源占用
- 无法控制 fetch size,Oracle 版本越新(如 19c+)预取行为越激进,可能一次拉几百行进 PGA,而你完全感知不到
- 不能中断后从断点继续(
%ROWCOUNT不可跨循环保留),重跑就得全量重查,对大表极不友好
真正需要流式可控处理时,显式游标 + BULK COLLECT LIMIT + 显式 COMMIT 是更透明、更可测的选择。别忘了查 v$pgastat 看真实 PGA 使用,而不是只盯着 open_cursors 参数调大——那只是把内存问题换成更隐蔽的性能抖动。


















