游标未显式关闭是pga内存泄漏主因,须open/close配对且异常时也关闭;bulk collect需加limit并及时清空集合;open_cursors调高治标不治本,应监控pga使用。
open 之后不 close,是游标内存泄漏最常见原因。oracle 不会自动回收未关闭的游标所占的 pga 内存,尤其在 pl/sql 块中反复打开、却依赖作用域退出隐式关闭时,极易堆积。
显式关闭游标比依赖作用域更可靠
PL/SQL 中声明的显式游标(DECLARE CURSOR c1 IS ...)在块结束时 *可能* 被隐式关闭,但前提是:
- 游标未被传递出当前作用域(如作为参数返回、赋值给包级变量)
- 没有发生异常导致控制流跳过正常结束路径
- 使用的是标准 PL/SQL 块,而非某些嵌入式环境(如某些 JDBC thin driver 的匿名块封装)
更稳妥的做法是:所有 OPEN 都配对 CLOSE,且放在 EXCEPTION 和正常流程的末尾
DECLARE
CURSOR c1 IS SELECT * FROM big_table WHERE status = 'A';
r c1%ROWTYPE;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO r;
EXIT WHEN c1%NOTFOUND;
-- 处理逻辑
END LOOP;
CLOSE c1; -- 必须写
EXCEPTION
WHEN OTHERS THEN
IF c1%ISOPEN THEN CLOSE c1; END IF; -- 异常时也确保关闭
RAISE;
END;
批量处理时慎用 BULK COLLECT 无限制加载
BULK COLLECT INTO 会一次性把结果集全拉进 PGA,若没加 LIMIT,等于把整张大表读进内存:
- 错误写法:
BULK COLLECT INTO t_array FROM ...(无 LIMIT) - 正确写法:
FETCH c1 BULK COLLECT INTO t_array LIMIT 1000;
注意:
-
t_array是TABLE OF ...类型,其大小随 LIMIT 增长,但不会无限膨胀 - 每次
FETCH ... LIMIT n后应立即处理并清空集合(t_array.DELETE),否则上一批数据仍驻留内存 - 若用
FORALL INSERT回写,同样要分批,避免 PGA 突增
open_cursors 参数不是万能解药
调高 open_cursors(如设为 1000)只能缓解“ORA-01000: maximum open cursors exceeded”报错,但掩盖了真正问题:
- 真实瓶颈常是 PGA 内存不足,而非游标句柄数不够
- 查看当前打开游标数:
SELECT SUM(a.value) FROM v$sesstat a, v$statname b WHERE a.statistic# = b.statistic# AND b.name = 'opened cursors current'; - 更关键的是查 PGA 使用:
SELECT name, value/1024/1024 "MB" FROM v$pgastat WHERE name IN ('total PGA allocated', 'total PGA used mem'); - 如果 “total PGA used mem” 接近或超过
pga_aggregate_target,说明该调参或改代码,而不是只加open_cursors
游标生命周期管理最容易被忽略的点
- 包级游标(定义在包体或包头中的CURSOR)**不会在过程结束后自动关闭**,必须显式 CLOSE,否则一直占用 PGA 直到会话断开
- 使用 REF CURSOR 时,如果由调用方打开,必须由调用方关闭;若在函数内打开并返回,文档需明确标注“调用方负责关闭”
- JDBC 中 ResultSet.close() 对应 Oracle 的游标关闭,但若使用了 setFetchSize(),底层仍可能缓存多行,需确认驱动版本是否支持真正的流式读取(如 ojdbc8 的 setFetchDirection(ResultSet.FETCH_FORWARD) + 小 fetch size)
游标本身不占多少内存,但它的结果集缓冲、排序区、哈希区全堆在 PGA 里——关不关游标,决定的是这部分内存何时释放。











