直接用单条JOIN SQL替代循环内SQL,避免N次解析与调用;若必须分步,则用BULK COLLECT加LIMIT批量加载、FORALL批量DML,并确保关联字段有索引。

直接在PL/SQL循环里反复执行SQL(尤其是带WHERE条件的单行查询),是Oracle性能杀手——它不是慢一点,而是慢一个数量级。根源在于每次循环都触发一次SQL解析、执行、网络往返和上下文切换。
为什么FOR r1 IN (SELECT ...) LOOP SELECT ... FROM t2 WHERE id = r1.id 会崩
这不是“写法不够优雅”的问题,而是执行模型错位:
- 外层游标返回N行,内层SQL就被执行N次,等于N次独立硬解析(未绑定变量时)或软解析(已绑定但未复用)
- 每次调用都产生一次用户态→内核态切换 + SQL*Net message from client等待
- V$SESSION_EVENT里会出现大量
library cache lock或SQL*Net message from client等待事件 - 索引再好也救不了:它只加速单次查找,无法消除N次调用本身的开销
优先用单条JOIN SQL替代嵌套循环
把“对A表每行查B表匹配项”这种逻辑,直接交给优化器做哈希连接或嵌套循环连接,而不是自己在PL/SQL里模拟。
- 错误写法:
FOR r1 IN (SELECT id, name FROM t1) LOOP FOR r2 IN (SELECT x FROM t2 WHERE t2.ref_id = r1.id) LOOP ... END LOOP; END LOOP; - 正确写法(首选):
FOR r IN (SELECT t1.id, t1.name, t2.x FROM t1 JOIN t2 ON t1.id = t2.ref_id) LOOP ... END LOOP; - 如果必须区分“有匹配”和“无匹配”,用
LEFT JOIN+r2.x IS NOT NULL判断,别写EXISTS子查询嵌套在循环里
BULK COLLECT + FORALL 是退而求其次的方案
当业务逻辑无法合并成单SQL(比如要调用PL/SQL函数做中间计算),才考虑批量拉取+批量DML。
-
BULK COLLECT INTO必须加LIMIT n,否则大数据量会撑爆PGA,报ORA-04030 - 用
FORALL i IN 1..t1_data.COUNT时,确保t1_data非空,否则i范围越界报ORA-06533 - 不要在
FORALL里混用SAVE EXCEPTIONS和非批量语句——异常处理逻辑会破坏批量语义 - 若内表t2需按t1.id做精确更新,且t2无索引支撑ref_id字段,
FORALL仍可能全表扫描多次,此时先建索引比改PL/SQL更有效
别信“加索引就能救嵌套循环”
索引只能让单次SELECT ... WHERE t2.id = :v1快一点,但无法改变N×M次调用的事实。真正该问的是:这个逻辑真的必须在PL/SQL层做吗?
- 检查是否漏掉了可下推的过滤条件——把
WHERE提前到JOIN里,比在循环里IF判断省十倍开销 - 确认驱动表选择:如果t1返回10万行,t2只有100行,那应该以t2为驱动表写反向JOIN,而不是死守“先查t1”的直觉
- EXPLAIN PLAN里看到
NESTED LOOPS但ROWS_PROBED远大于ROWS_PROCESSED,说明优化器被误导了,得加/*+ USE_HASH(t1 t2) */强制改用哈希连接


















