在Oracle 12c中处理多行数据集,优先用FOR循环游标或BULK COLLECT + FORALL;显式游标仅在需逐行精细控制(如动态分支、条件跳过、中间日志)时才用,因其手动管理易漏CLOSE致ORA-01000,且FETCH频繁触发上下文切换,性能低于自动优化的FOR循环和批量操作。

直接说结论:在 Oracle 12c 中处理多行数据集,优先用 FOR 循环游标或 BULK COLLECT + FORALL,显式游标配 OPEN/FETCH/CLOSE 仅在需要逐行精细控制(如动态逻辑分支、条件跳过、中间日志)时才用。
为什么显式游标四步法在12c里已不是首选
Oracle 12c 的 PL/SQL 引擎对游标循环做了深度优化,但显式游标仍要手动管理生命周期,容易漏 CLOSE 导致 ORA-01000;更关键的是每次 FETCH 都是一次上下文切换,性能损耗明显。而 FOR 循环游标由系统自动管理打开、取数、关闭,且底层做了批量预取(prefetch),实际执行更快、代码更安全。
-
FOR循环写法简洁,不会出现%NOTFOUND判断位置错误导致多取一行或少取一行 - 不支持在循环中修改游标定义(比如动态 WHERE 条件),但绝大多数业务场景不需要这种灵活性
- 无法在循环中途
EXIT后再继续从断点取数——它是一次性遍历,适合“全量处理”类任务
真正高效的做法:BULK COLLECT + FORALL 替代逐行 FETCH
当你要对结果集做批量 DML(比如更新几千条记录),硬扛着用 FETCH ... INTO 逐行 UPDATE 是最慢的。12c 支持一次取多行进内存数组,再一次性发给 SQL 引擎执行,大幅减少上下文切换次数。
- 用
BULK COLLECT INTO把查询结果批量载入INDEX BY TABLE或VARRAY,推荐用emp_demo%ROWTYPE类型的集合 - 配合
LIMIT子句分批处理,避免大集合撑爆 PGA 内存,例如FETCH emp_cur BULK COLLECT INTO emp_list LIMIT 1000 - 用
FORALL i IN 1..emp_list.COUNT批量执行 DML,注意FORALL不会自动回滚失败项,需搭配SAVE EXCEPTIONS和SQL%BULK_EXCEPTIONS捕获具体哪几行出错
示例片段:
DECLARE
CURSOR emp_cur IS SELECT empno, sal FROM emp_demo WHERE deptno = 30;
TYPE emp_tab_type IS TABLE OF emp_cur%ROWTYPE;
emp_list emp_tab_type;
BEGIN
OPEN emp_cur;
LOOP
FETCH emp_cur BULK COLLECT INTO emp_list LIMIT 100;
EXIT WHEN emp_list.COUNT = 0;
<pre class='brush:php;toolbar:false;'>FORALL i IN 1..emp_list.COUNT SAVE EXCEPTIONS
UPDATE emp_demo SET sal = emp_list(i).sal * 1.05
WHERE empno = emp_list(i).empno;
COMMIT;END LOOP; CLOSE emp_cur; EXCEPTION WHEN OTHERS THEN IF emp_cur%ISOPEN THEN CLOSE emp_cur; END IF; RAISE; END;
哪些场景还必须用显式游标 + 手动 FETCH
不是所有多行处理都能靠 FOR 或 BULK 解决。以下情况绕不开传统显式游标流程:
- 需要根据当前行的值决定是否跳过下一行(比如遇到状态为 'CANCELLED' 就
CONTINUE,不进后续逻辑) - 要在循环中调用另一个存储过程并传入当前行字段,且该过程可能抛异常,你得捕获后只跳过这一行而非中断整个循环
- 需要在
FETCH后立刻检查%FOUND做分支(例如某些老系统要求“查不到就插入,查到就更新”,且不能用MERGE) - 游标带
FOR UPDATE且需在循环中根据业务规则选择性UPDATE或ROLLBACK TO SAVEPOINT
这时务必把 CLOSE 放进 EXCEPTION 块和正常路径末尾,否则连接池里游标越积越多,ORA-01000 几乎必然发生。
容易被忽略的 12c 特性:游标属性的细微差异
12c 对游标属性行为做了明确规范,但很多人还在用旧习惯写错。重点注意:
-
%ROWCOUNT在OPEN后是 0,FETCH第一次后是 1,但FOR循环里它始终反映已处理行数,不是“当前行号” -
%NOTFOUND只有在FETCH尝试取数但没取到时才为TRUE;如果游标刚OPEN就查不到数据,第一次FETCH后%NOTFOUND才生效,不能拿它判断“查询是否为空” - 隐式游标
SQL%属性和显式游标cur_name%属性不能混用——比如在显式游标循环里写IF SQL%FOUND THEN是无效的,永远返回上一条 DML 的状态
最后强调一点:无论选哪种方式,只要涉及多行处理,先问自己一句——这个逻辑能不能用单条 UPDATE / MERGE / WITH 语句重写?能的话,就别碰游标。这是 12c 下最高效也最不容易出错的底线。


















