显式游标需手动管理open/close,漏关导致ora-01000错误;推荐用for循环隐式管理或bulk collect+limit批量处理;性能瓶颈多源于底层sql,须优化查询计划与索引。

显式游标必须手动管理生命周期
PL/SQL中显式游标不会自动打开或关闭,漏掉 CLOSE 是最常见资源泄漏原因。每个 OPEN 都对应一次查询执行和内存分配,未关闭的游标会持续占用会话级游标句柄(受 OPEN_CURSORS 参数限制),长期运行的存储过程容易触发 ORA-01000: maximum open cursors exceeded 错误。
实操建议:
- 所有
OPEN后必须配对CLOSE,且不能依赖“过程结束自动释放”——PL/SQL 不保证这点 - 在
EXCEPTION块中重复写CLOSE,或用BEGIN ... EXCEPTION ... WHEN OTHERS THEN CLOSE cur; RAISE;确保异常路径也能释放 - 避免在循环内反复
OPEN/CLOSE同一游标,这属于典型低效写法
用 FOR 循环替代 OPEN-FETCH-CLOSE 手动流程
FOR rec IN cursor_name LOOP ... END LOOP; 不仅语法简洁,更重要的是它隐式完成 OPEN、逐行 FETCH、最后 CLOSE,且内部做了优化:比如避免重复检查 %NOTFOUND、减少上下文切换开销。手动写 LOOP + FETCH + EXIT WHEN 容易出错,也更慢。
注意点:
-
FOR循环中的rec是隐式声明的记录变量,类型自动匹配游标 SELECT 列表,无需提前定义%ROWTYPE - 不能在
FOR循环体内对游标执行UPDATE ... WHERE CURRENT OF—— 因为游标是只读遍历,如需更新,必须改用带FOR UPDATE的显式游标 + 手动FETCH - 若需提前退出循环(如找到第一条匹配就停),直接用
EXIT即可,FOR循环仍会自动CLOSE
大数据量时优先用 BULK COLLECT + LIMIT
单行 FETCH 在处理万级以上记录时性能急剧下降,因为每次调用都涉及 PL/SQL 与 SQL 引擎间的数据拷贝和上下文切换。Oracle 提供 BULK COLLECT 一次性取多行到集合,配合 LIMIT 分批控制内存占用。
示例关键写法:
DECLARE
TYPE t_dept_tab IS TABLE OF dept%ROWTYPE;
l_depts t_dept_tab;
CURSOR c_dept IS SELECT * FROM dept;
BEGIN
OPEN c_dept;
LOOP
FETCH c_dept BULK COLLECT INTO l_depts LIMIT 100;
EXIT WHEN l_depts.COUNT = 0;
-- 对 l_depts 进行批量处理,例如 FORALL INSERT / UPDATE
END LOOP;
CLOSE c_dept;
END;
要点:
-
LIMIT值不是越大越好,通常 50–500 之间较平衡;设为 0 会一次性加载全部结果,可能 OOM -
BULK COLLECT不触发隐式提交,但集合变量本身占 PGA 内存,大表需监控v$sesstat中session pga memory - 后续若需
FORALL批量 DML,必须确保集合非空,否则FORALL报错
游标定义阶段就该考虑 SQL 性能
游标慢,90% 源头在它绑定的 SELECT 语句。PL/SQL 游标只是“包装”,真正耗时的是底层查询执行计划。别指望靠换游标写法来救慢 SQL。
排查顺序应是:
- 先用
EXPLAIN PLAN FOR或 PL/SQL Developer 的 F5 执行计划功能,确认游标内SELECT是否走索引、是否全表扫描 - 检查 WHERE 条件字段是否有合适索引,尤其注意函数包裹列(如
UPPER(name))会导致索引失效 - 避免在游标
SELECT中调用自定义函数(尤其是非DETERMINISTIC或未声明IMMUTABLE的),这会让优化器无法估算代价,还可能引发每行都调用的性能雪崩 - 如果游标用于触发器内(如
BEFORE INSERT),更要警惕——慢游标会让 DML 变成阻塞操作
最常被忽略的一点:游标里加 FOR UPDATE 本意是锁行,但若没配 WHERE 条件或条件不走索引,可能升级成表锁,直接卡死其他会话。











