应使用BULK COLLECT + FORALL替代逐行FOR循环,因其避免PL/SQL与SQL引擎频繁上下文切换、提升I/O并行性、减少CPU开销;错误用法会导致ORA-06502、ORA-01403等异常及性能劣化。
直接结论:别用逐行for循环处理结果集,改用 bulk collect + forall,否则90%的执行时间花在pl/sql和sql引擎切换上。
每次 FETCH 一行、UPDATE 一行,都会触发一次PL/SQL引擎与SQL引擎之间的上下文切换——不是简单跳转,而是保存内存状态、重建执行环境、维护游标位置。实测10万次切换能吃掉70%以上CPU时间;更糟的是,它强制把本可并行的I/O变成串行,让索引和Buffer Cache几乎失效。
常见错误现象:
PL/SQL lock timer 或 PGA memory operation
SQL_TRACE 显示解析时间占比超30%,而实际计算逻辑不到5%核心是把“循环控制权”从PL/SQL层移交到SQL层,用数组做中转站:
TYPE data_tab IS TABLE OF your_cursor%ROWTYPE;(别用VARRAY或关联数组)BULK COLLECT INTO 必须带 LIMIT(如 LIMIT 1000),否则大结果集直接OOM,报 ORA-04030
FORALL i IN 1..v_data.COUNT 的下标必须连续;若中间有删元素,改用 INDICES OF v_data
FORALL 里不能写函数调用(如 UPPER(v_data(i).name)),所有转换必须在FORALL外提前算好ROWID 或主键:WHERE rowid = v_data(i).rid 比 WHERE id = v_data(i).id 更稳,避免二次查询这些错误不报语法错,但一跑就崩或静默失败:
ORA-06502:集合字段长度不匹配,比如VARCHAR2(10)字段塞了15字节值;或集合为空却没判 v_data.COUNT = 0 就进 FORALL
ORA-01403:用了 FORALL ... SAVE EXCEPTIONS 却没立刻查 SQL%BULK_EXCEPTIONS.COUNT,导致异常被忽略后下一条继续报错%NOTFOUND 判断 BULK COLLECT 结束——它只对单行FETCH有效,必须用 v_data.COUNT = 0
BULK COLLECT 超5万行且PGA受限(如OLTP系统限制256MB),反而引发磁盘交换;此时应坚持分批,但每批仍用 LIMIT 1000 + FORALL,而非退化成逐行批量不是银弹:
FOR rec IN (SELECT ...) LOOP,再配合 MOD(i, 100) = 0 做中间提交真正难的是判断“哪些逻辑能拆、哪些必须串行”,而不是记住语法——多数人卡在这一步,不是不会写FORALL,是不敢动原有业务语义。