游标不是提速工具而是性能杀手;应优先用集合操作(如窗口函数、LAG/LEAD、递归CTE)替代,必须使用时需选对游标类型、加覆盖索引、避免循环内远程调用与事务控制。

游标本身不是提速工具,而是性能杀手;所谓“利用游标保证执行速度”是个伪命题——你真正需要的,是判断「是否真的必须用游标」,以及「如果非用不可,怎么让它慢得不那么明显」。
先确认:这业务逻辑真不能用集合操作重写?
99% 声称“必须逐行计算”的场景,其实只是没把条件和依赖关系理清楚。比如要对每个订单计算动态折扣率,且该折扣率依赖上一单的累计金额——听起来像必须游标,但用窗口函数 SUM() OVER (ORDER BY ... ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) 就能直接拿到前缀和,无需循环。
- 检查原游标体内的核心计算:是否只读取当前行字段?→ 可直接挪到
SELECT或UPDATE ... SET中 - 是否引用了上一行/下一行值?→ 优先尝试
LAG()/LEAD()或递归 CTE - 是否在循环里调用了标量函数或子查询?→ 这些会随行数线性放大开销,集合改写后可被优化器一次性展开
- MySQL 8.0+ 或 PostgreSQL 用户:试试
JSON_TABLE()或generate_series()拆解复杂中间状态
必须用游标时,选错类型会让速度再降 3–5 倍
SQL Server 默认的 DYNAMIC 游标每次 FETCH 都重查表、重走执行计划;Oracle 的 FOR UPDATE 游标若没加 WHERE CURRENT OF,更新时会全表扫描定位行。这些不是配置问题,是语义陷阱。
- SQL Server:强制声明为
STATIC READ_ONLY,确保结果集快照化,避免锁和版本控制开销 - Oracle:用
SELECT ... BULK COLLECT INTO批量取数,再用FORALL批量 DML,绕过单行上下文切换 - MySQL:没有
BULK COLLECT,但可用临时表中转:CREATE TEMPORARY TABLE tmp_cursor AS SELECT ...,再对临时表加索引后遍历 - 所有数据库:禁止在游标声明的
SELECT里用GETDATE()、NOW()等非确定性函数,否则无法生成稳定执行计划
游标底层查询没索引,等于裸奔跑马拉松
游标慢,80% 的时间花在打开阶段(OPEN)——即执行底层 SELECT 构建结果集。这时候缺索引,不是慢一点,是直接卡住。
- 用
SET STATISTICS XML ON(SQL Server)或EXPLAIN FORMAT=JSON(MySQL)抓游标SELECT的实际执行计划 - 重点看是否有
Table Scan、TempDB Spill或Using filesort;这些就是索引缺失的铁证 - 覆盖索引必须包含:所有
WHERE条件列 + 所有ORDER BY列 + 游标FETCH INTO用到的所有字段(即SELECT列表) - Oracle 用户注意:
INDEX FAST FULL SCAN不等于高效,它仍需排序;要的是INDEX RANGE SCAN直接命中
循环体内别碰远程表、函数或事务控制
每行都连一次外部 API、查一次链接服务器、或在循环里 COMMIT,等于把数据库当脚本解释器用。这些操作无法被批处理优化,开销完全线性增长。
- 远程访问:提前把远端数据拉到本地临时表,再与游标结果
JOIN - 标量函数:改写为内联表值函数(ITVF),或干脆物化成计算列+索引
- 事务控制:除非业务强要求每行独立提交(如金融冲正),否则整个游标块外层包一个事务,避免日志频繁刷盘
- 日志写入:不要在循环里插审计日志;改用
INSERT ... SELECT批量写入,或触发器异步落库
真正难的不是写游标,是说服自己放弃游标——那些“必须逐行”的假设,往往来自对集合运算表达力的低估。而一旦非用不可,最易被忽略的点是:游标性能瓶颈几乎总在 OPEN 阶段,不在 FETCH 循环里。

















