游标本身不直接耗尽资源,真正拖垮数据库的是长时间打开+频繁访问更新中的表,尤其未及时关闭且在循环中执行dml的显式游标;必须配对使用open/close,且fetch后须立即检查%notfound,否则引发ora-01001、pga内存溢出及undo阻塞。
游标本身不直接耗尽资源,真正拖垮数据库的是长时间打开 + 频繁访问更新中的表 —— 尤其是没及时关闭、又在循环里做 dml 的显式游标。
显式游标必须配对使用 OPEN / CLOSE,且不能漏掉 EXIT WHEN cursor%NOTFOUND
手动管理的显式游标(CURSOR c IS SELECT ...)一旦 OPEN,就会在 PGA 中保留结果集快照,并持续持有 undo 段用于一致性读。如果循环中忘记 CLOSE,或 FETCH 后没检查 %NOTFOUND 就继续 FETCH,会导致:
- ORA-01001: invalid cursor(
FETCH到末尾后还取) - PGA 内存持续增长,尤其配合大结果集时
- 长事务锁住 undo,阻塞其他会话的更新
正确写法示例:
DECLARE
CURSOR emp_cur IS SELECT empno, sal FROM emp WHERE deptno = 10;
v_empno emp.empno%TYPE;
v_sal emp.sal%TYPE;
BEGIN
OPEN emp_cur;
LOOP
FETCH emp_cur INTO v_empno, v_sal;
EXIT WHEN emp_cur%NOTFOUND; -- 关键:必须放 FETCH 后、处理前
-- 业务逻辑(避免在此处 COMMIT 或长耗时操作)
END LOOP;
CLOSE emp_cur; -- 必须显式关闭
END;
优先用 FOR ... IN ... LOOP 替代手动游标控制
这个语法糖本质是 Oracle 自动帮你做了 OPEN → FETCH → CLOSE,只要不中途异常退出,就不可能漏关游标。它还能自动适配 %ROWTYPE,减少变量声明错误。
- 不支持在循环体内
UPDATE/DELETE当前行(因为没WHERE CURRENT OF) - 不能提前退出后继续用同一游标(比如只处理前 100 行)
- 性能上和手动游标无本质差异,但安全系数高得多
示例:
DECLARE
CURSOR c IS SELECT empno, ename FROM emp WHERE sal > 2000;
BEGIN
FOR r IN c LOOP
DBMS_OUTPUT.PUT_LINE(r.empno || ': ' || r.ename);
-- 循环结束时 c 自动 CLOSE,无需干预
END LOOP;
END;
REF CURSOR 不解决资源问题,反而容易失控
REF CURSOR 是指针类型,常用于把游标结果集传给调用方(如 Java 应用),但它本身不改变资源生命周期。常见误区:
- 以为“动态”就能省资源 —— 实际上
OPEN refcur FOR 'SELECT ...'后,照样占用 PGA 和 undo - 在存储过程中多次
OPEN同一个REF CURSOR变量而不CLOSE,等于反复开新游标 - 把
REF CURSOR当集合变量用,循环里反复赋值打开,却没关旧的
若必须用 REF CURSOR,务必遵循:
- 每个
OPEN都配一个CLOSE(即使异常也要进EXCEPTION块关) - 避免在循环内重复
OPEN同一变量 - 除非明确要返回给客户端,否则别为了“灵活”而用它替代静态游标
批量处理比单行游标更省资源,但要注意 commit 策略
真要处理大批量数据(比如百万级更新),游标逐行处理就是反模式。应该:
- 改用
INSERT /*+ APPEND */ ... SELECT或MERGE批量语句 - 若逻辑复杂必须 PL/SQL 处理,用
BULK COLLECT+FORALL,一次 fetch 100–500 行进内存,再批量 DML -
FORALL后不要每批都COMMIT,按业务容忍度设提交点(比如每 10000 行 commit 一次)
典型风险点:
-
BULK COLLECT INTO不设LIMIT→ 一次性加载全部结果到 PGA → OOM -
FORALL里混用SAVE EXCEPTIONS却不检查SQL%BULK_EXCEPTIONS→ 错误被吞掉
最容易被忽略的是:游标是否真的需要?很多场景下,一条带子查询或分析函数的 UPDATE 就能替代整个游标循环。先看执行计划,再决定要不要游标 —— 这比优化游标本身重要十倍。











