循环慢的根源在于重复执行低效操作:每次循环触发独立SQL查询或排序、隐式类型转换致索引失效、统计信息过期导致执行计划失准,以及累积性资源消耗。

循环本身不慢,慢的是你在循环里反复干了不该干的事——尤其是每次循环都触发一次独立的 SQL 查询或排序。
FORALL 被写成普通 FOR 循环
常见错误是把本该批量处理的更新/插入,写成 FOR i IN 1..cnt LOOP UPDATE ... WHERE id = arr(i); END LOOP;。这会导致:每轮循环都发起一次上下文切换、一次解析、一次执行、一次网络往返(如果走客户端)。1000 行就是 1000 次 SQL 引擎调用。
- 正确做法是改用
FORALL i IN 1..arr.COUNT UPDATE ... WHERE id = arr(i); -
FORALL不会自动提交,记得加COMMIT或控制LIMIT分批 - 如果更新逻辑复杂、需逐行判断,至少把 WHERE 条件能收敛的部分提前用
BULK COLLECT INTO拉到内存再处理,别在循环里反复查表
循环中嵌套 SELECT + ORDER BY
比如写成 FOR rec IN (SELECT * FROM t1) LOOP SELECT ... FROM t2 WHERE x = rec.x ORDER BY y; END LOOP;,表面看只是“查一行”,实际每次都在 t2 上做一次全表扫描+磁盘排序(尤其没索引或 PGA 不足时)。
- ORDER BY 字段没复合索引?
CREATE INDEX idx_t2_x_y ON t2(x, y);可让外层循环直接走索引范围扫描+避免排序 - 如果
t2数据量大,优先考虑 JOIN 替代嵌套查询:SELECT t1.*, t2.y FROM t1 JOIN t2 ON t1.x = t2.x ORDER BY t2.y; - 确认是否真需要每次排序:若只取 Top-1,改用
SELECT ... FROM t2 WHERE x = rec.x AND ROWNUM = 1 ORDER BY y并确保有索引支撑
隐式类型转换导致索引失效
循环变量类型和表字段类型不一致,比如 id 是 NUMBER,但你传入字符串 '123',Oracle 自动转成 TO_NUMBER('123'),导致索引列被函数包裹,全表扫描不可避免。
- 检查执行计划:出现
TABLE ACCESS FULL且 WHERE 条件明确写了字段名,大概率是隐式转换 - 统一类型:PL/SQL 变量声明为
my_id t2.id%TYPE,而不是my_id VARCHAR2(20) - WHERE 子句里别写
col = TO_CHAR(v_date),改用col = v_date(前提是 col 是 DATE 类型)
统计信息过期或绑定变量窥探失效
循环第一次跑快,后面越来越慢,往往不是代码问题,而是优化器“看走眼”了:它基于第一次绑定值生成的执行计划,后续不同值导致实际数据分布差异巨大,却还在硬套旧计划。
- 查
v$sql看同一语句的PLAN_HASH_VALUE是否唯一;若多个值对应不同 hash,说明计划不稳定 - 对关键表定期
DBMS_STATS.GATHER_TABLE_STATS,特别是数据变动 >10% 后 - 慎用
/*+ BIND_AWARE */hint,12c+ 可考虑自适应游标共享,但不如先保证统计信息新鲜
最易被忽略的一点:循环变慢常不是单次 SQL 的问题,而是累积效应——比如每次循环都往一个没主键的临时表插数据,又不清理,后续查询越来越卡;或者用了自治事务却忘了异常路径下的回滚,锁越积越多。盯住 v$session_wait 和 v$active_session_history,比猜更准。


















