游标慢的根本原因是其执行模型:服务端逐行拉取+客户端逐行处理,每次FETCH都触发状态切换、内存拷贝、控制流跳转及隐式锁等待,无法媲美UPDATE...JOIN等单次解析批量执行的集合操作。

游标慢的根本原因不是写法,是执行模型
MySQL游标本质是服务端逐行拉取+客户端(存储过程内部)逐行处理,每次FETCH都触发一次内部状态切换和内存拷贝。哪怕结果集只有1万行,也意味着至少1万次控制流跳转、1万次变量赋值、以及隐式锁等待——这和UPDATE ... JOIN这种单次解析、批量执行的集合操作完全不在一个量级。
哪些游标场景能直接用集合操作替代
只要不涉及「每行调用外部API」「每行做独立HTTP请求」「每行触发不同存储过程」这类无法并行化的逻辑,绝大多数游标都可以改写。常见可替换模式包括:
-
INSERT INTO t2 SELECT ... FROM t1 WHERE ...替代遍历t1插入t2 -
UPDATE t1 JOIN t2 ON ... SET t1.x = t2.y替代逐行查t2再更新t1 -
DELETE t1 USING t1 JOIN t3 ON ... WHERE t3.flag = 'done'替代先查ID再删 - 用
GROUP_CONCAT()或窗口函数预聚合,避免在循环里拼字符串
改写时必须检查的三个陷阱
集合操作看似简单,但踩坑后反而比游标更难调试:
- 原游标里有
IF ... THEN ... ELSE分支更新不同字段?→ 改成CASE WHEN表达式,别拆成多条UPDATE - 游标中用了
SELECT ... INTO取单值再参与计算?→ 改成子查询或JOIN,注意NULL传播问题 - 原逻辑依赖上一行处理结果(比如累计求和)?→ 不能硬套集合操作,得用窗口函数
SUM() OVER (ORDER BY ...),否则要接受游标保留
实在绕不开游标时的底线优化
如果业务强要求逐行处理(如调用sys_exec()或写日志表),至少守住这几条:
- 在
DECLARE CURSOR的SELECT里加WHERE条件和索引字段,把结果集压到最小 - 关闭自动提交:
SET autocommit = 0,并在循环外显式COMMIT,避免每行一个事务 - 用
FETCH cur INTO @v1, @v2而非局部变量,减少变量声明开销 - 删除所有循环内的
SELECT查询——它们不会返回给客户端,纯属浪费CPU
游标不是不能用,而是它一旦出现在执行计划里,就说明你已经放弃了MySQL最擅长的批量能力。真正难的不是写出能跑的游标,是看出哪一行SQL其实根本不需要游标。











