游标循环慢主因是误用:本该sql一次性完成的关联、过滤、聚合被拆成pl/sql逐行处理;for r in (select...)循环每次迭代都触发完整解析+执行+获取流程,导致1万次单行查询、高逻辑读与上下文切换开销。

游标循环慢,90%不是游标本身的问题,而是你把它用在了不该用的地方——尤其是把本该由SQL引擎一次性完成的关联、过滤、聚合,硬拆成PL/SQL层的逐行处理。
为什么FOR r IN (SELECT ...)循环比等效SQL慢一个数量级
表面看只是写法不同,实际执行模型天差地别:
- 每次循环迭代都触发一次独立的「解析 + 执行 + 获取」流程,哪怕用了绑定变量,软解析和一致性读判断仍不可免
- 外层1万条记录 × 内层每次查一张表 → 实际发出1万次单行查询,逻辑读轻松破20万
- 上下文在SQL引擎和PL/SQL引擎之间反复切换,每轮至少2次用户态/内核态开销
-
DBMS_OUTPUT.PUT_LINE这类调试输出在循环里调用,会进一步放大延迟(尤其网络往返场景)
优先用单SQL JOIN替代嵌套循环逻辑
别试图“优化循环”,先问一句:这个逻辑能不能用一条SQL表达?绝大多数能。
- 错误写法:
FOR r1 IN (SELECT id FROM t1) LOOP SELECT x FROM t2 WHERE ref_id = r1.id; END LOOP; - 正确路径一(首选):
FOR r IN (SELECT t1.id, t2.x FROM t1 JOIN t2 ON t1.id = t2.ref_id) LOOP ... END LOOP; - JOIN后数据量大?加
WHERE条件或LIMIT子句控制结果集大小,避免PGA溢出 - 如果必须分批处理,用
ROWNUM或OFFSET/FETCH(12c+)切片,而不是靠PL/SQL循环模拟
BULK COLLECT + FORALL不是备选,是默认动作
当SQL无法一步到位(比如需动态拼条件、跨多库、或含复杂业务判断),BULK COLLECT就是底线方案,不是“高级技巧”。
- 必须加
LIMIT n,例如BULK COLLECT INTO l_data LIMIT 1000,否则大数据量直接报ORA-04030 - 集合变量使用前要初始化:
l_data := t_data();,否则l_data.COUNT会报ORA-06531 - 后续处理尽量在内存中做:
FOR i IN 1..l_data.COUNT LOOP ... END LOOP;,别再回表查 - 更新/插入用
FORALL i IN INDICES OF l_data,避免VALUES OF引发隐式UNION ALL扫描
显式游标和SYS_REFCURSOR的关闭陷阱
自动关闭只对FOR r IN cursor_name或FOR r IN (SELECT...)生效;其他情况不关=泄漏。
- 显式声明的游标(
CURSOR c IS SELECT...)可用FOR r IN c LOOP,循环结束自动CLOSE -
SYS_REFCURSOR变量不支持FOR IN语法,必须OPEN/FETCH/CLOSE三件套,漏掉CLOSE会导致游标句柄堆积 - 需要提前退出循环?必须用
FETCH ... INTO+EXIT WHEN c%NOTFOUND,FOR循环里拿不到%NOTFOUND - 游标传参给子过程?只能传
SYS_REFCURSOR,此时关闭责任落在接收方,务必文档约定清楚
最常被忽略的一点:性能问题往往不出现在游标定义处,而出现在游标背后的SQL上——检查V$SQL_PLAN里那条语句是否真走了索引、有没有SORT ORDER BY磁盘排序、ROWS_PROBED是不是远大于ROWS_PROCESSED。游标只是镜子,照出的是SQL和数据分布的真实状况。











