游标慢本质是执行模型不匹配,应优先用集合操作替代:update join、insert select、group_concat、窗口函数等;仅当需调用外部系统等特殊场景才用游标,并优化索引、事务、变量及子查询。

游标慢不是写法问题,是执行模型天生不匹配
MySQL游标本质是服务端逐行拉取 + 存储过程内部逐行处理,每次 FETCH 都触发一次状态切换、内存拷贝、变量赋值和隐式锁等待。哪怕结果集只有 1 万行,也是 1 万次独立控制流跳转——这和 UPDATE JOIN 或 INSERT SELECT 这类单次解析、批量执行的集合操作完全不在一个量级。
所以别纠结“游标怎么写得更优雅”,先问:这逻辑真需要逐行?
- 如果只是把表 A 的数据按条件更新到表 B → 直接用
UPDATE t1 JOIN t2 ON ... SET ... - 如果要拼接字符串(如逗号分隔的 ID 列表)→ 改用
GROUP_CONCAT()或窗口函数预聚合 - 如果循环里还嵌套了
SELECT查关联表 → 必然触发 N×M 查询放大,必须提前JOIN或写入临时表
哪些游标场景能直接用集合操作替代
只要不涉及每行调用外部系统(如 SYS_EXEC())、每行发 HTTP 请求、或每行触发不同存储过程,绝大多数游标都能改写。
-
INSERT INTO t2 SELECT ... FROM t1 WHERE ...替代遍历t1插入t2 -
DELETE t1 USING t1 JOIN t3 ON t1.id = t3.ref_id WHERE t3.status = 'done'替代先查 ID 再删 - 分支更新不同字段?→ 改成
CASE WHEN表达式,不要拆成多条UPDATE - 原逻辑依赖上一行结果(比如累计求和)?→ 不能硬套集合操作,必须用
SUM() OVER (ORDER BY ...)窗口函数
实在绕不开游标时的底线优化
业务强要求逐行(比如写审计日志、调用 UDF、触发异步任务),至少守住这四条:
- 在
DECLARE CURSOR的SELECT里加精准WHERE条件,并确保走索引,把结果集压到最小 - 显式关闭自动提交:
SET autocommit = 0,并在循环外统一COMMIT,避免每行一个事务 - 用
FETCH cur INTO @v1, @v2(会话变量)而非DECLARE v1 INT(局部变量),减少变量声明开销 - 删掉循环体内所有
SELECT——它们不返回客户端,纯属浪费 CPU 和连接资源
游标里嵌套子查询是最隐蔽的性能杀手
存储函数/过程中没法用 PREPARE,开发者常靠多层子查询“硬凑”逻辑。但这类查询极易让优化器误判:单独跑很快,塞进游标循环后整体耗时飙升;EXPLAIN 显示 type=ALL 或 rows 异常高;且函数内联限制导致执行计划无法复用。
- 高频子查询 → 拆成
CREATE VIEW,函数中直接SELECT * FROM view_name - 中间结果不稳定 → 用
CREATE TEMPORARY TABLE tmp AS (SELECT ...)承接,再对tmp做后续操作 - 检查
WHERE是否失效:比如WHERE YEAR(create_time) = 2024、WHERE user_id = '123'(隐式类型转换)、LIKE '%abc'
真正难的不是写出能跑的游标,是看出哪一行 SQL 其实根本不需要游标——尤其是当 SELECT 本身已经能表达完整逻辑时。











