游标循环慢的根本原因是逐行处理引发大量上下文切换和磁盘i/o,而非游标本身;应优先用bulk collect+forall批量处理,避免循环内嵌套查询或dml,能用单条sql实现的逻辑绝不使用游标。

为什么游标循环一跑就慢
根本原因不是游标本身,而是逐行处理触发了大量上下文切换和磁盘 I/O。比如一个含 10 万行的游标,如果每次 FETCH 后都执行一次独立 UPDATE 或查另一张表,实际会发起 10 万次 SQL 解析、执行、结果集返回——这比单条批量语句慢一个数量级。
用 BULK COLLECT + FORALL 替代单行 FETCH
这是最直接有效的提速手段。把数据一次性捞进内存数组,再批量提交,能大幅减少上下文切换次数。
-
BULK COLLECT INTO必须配合集合类型(如TYPE emp_tab IS TABLE OF employees%ROWTYPE),不能直接塞进标量变量 - 注意内存控制:默认不限制条数,大数据量可能 OOM;建议加
LIMIT n分批,比如每次 1000 行 -
FORALL i IN INDICES OF emp_records比FORALL i IN 1..emp_records.COUNT更安全,能跳过稀疏数组中的空槽 -
FORALL不做事务回滚粒度控制——整批失败则全部回滚,若需部分成功,得用SAVE EXCEPTIONS配合%BULK_EXCEPTIONS
DECLARE
TYPE emp_tab IS TABLE OF employees%ROWTYPE;
emp_records emp_tab;
BEGIN
SELECT * BULK COLLECT INTO emp_records
FROM employees WHERE department_id = 10 LIMIT 1000;
<p>FORALL i IN INDICES OF emp_records
UPDATE employees SET salary = salary * 1.1
WHERE employee_id = emp_records(i).employee_id;
END;</p>
别在游标循环里嵌套查询或 DML
这是性能杀手。常见错误是:游标查出主表 ID,循环里再用这个 ID 去查关联表或更新日志表。Oracle 每次都要重新解析、走索引、读块——10 万次就是 10 万次物理读。
- 把所有需要的数据,提前在游标
SELECT中用JOIN或子查询拉全,避免循环内二次访问 - 如果必须关联外部数据,优先考虑用
MERGE或WITH+UPDATE替代循环逻辑 -
INSERT在循环里通常可接受(尤其用FORALL),但UPDATE/DELETE绝对禁止出现在循环体中 - 实在绕不开嵌套查询时,确保被查表有合适索引,且查询条件能走索引范围扫描,避免全表扫
什么时候该彻底放弃游标
游标不是万能解法。只要业务逻辑能用单条 SQL 表达,就不要写游标。
- 统计、累加、分组聚合 → 直接用
GROUP BY+ 聚合函数 - 字段计算更新 → 用
UPDATE ... SET col = expr WHERE ... - 多表数据同步 → 用
MERGE或INSERT ALL+SELECT - 生成序列号或编号 → 用
ROW_NUMBER()等窗口函数 - 真正需要过程化逻辑的场景极少:比如某字段值依赖前一行计算结果、或需调用 PL/SQL 函数做复杂校验
游标真正的价值在于「无法用声明式 SQL 描述的顺序依赖逻辑」,而不是「因为习惯所以用了游标」。多数慢游标,本质是 SQL 设计没想透。











