游标是最后手段,仅在跨库调用、动态SQL、状态依赖更新等集合操作失效时才使用;MySQL中必须配NOT FOUND处理器,不支持WHERE CURRENT OF,需用主键显式更新,避免嵌套与循环内提交。

游标不是“能用就用”的工具,而是“别无选择时才扛着用”的最后手段。当业务逻辑涉及跨库调用、动态SQL拼接、状态依赖更新(比如上一行结果决定下一行操作)、或需在循环中嵌套事务控制时,集合操作彻底失效,游标才真正不可替代。
MySQL存储过程中声明游标必须配DECLARE HANDLER
MySQL的游标没有内置的“到达末尾自动退出”机制,FETCH失败后@@FETCH_STATUS变为 -1,但若不显式捕获,后续WHILE @@FETCH_STATUS = 0会无限循环或跳过最后一行。常见错误是漏写NOT FOUND处理器:
- 必须在
DECLARE cursor_name CURSOR FOR ...之后、OPEN之前,加一句:DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;(其中done是你自己定义的BOOLEAN或INT变量) - 不能只靠
@@FETCH_STATUS判断——MySQL里这个变量在某些版本中行为不稳定,尤其在子查询或嵌套游标中 - 如果游标查询本身可能返回空集,
done初始值必须为FALSE,否则循环直接跳过
UPDATE WHERE CURRENT OF 在MySQL中根本不可用
很多教程照搬SQL Server或Oracle写法,写UPDATE ... WHERE CURRENT OF cursor_name,但在MySQL中这句语法直接报错:ERROR 1325 (HY000): Cursor is not open或Unknown column 'cursor_name' in 'where clause'。MySQL不支持CURRENT OF语义。
- 所有修改必须显式用主键或唯一标识字段做
WHERE条件,例如:UPDATE users SET status = 'processed' WHERE id = @user_id; - 如果原查询没选主键,必须提前在
SELECT里加上,否则无法安全定位当前行 - 高并发下要防重复处理:可在UPDATE里加条件限制,如
WHERE id = @user_id AND status = 'pending',并检查ROW_COUNT()是否为1
嵌套游标导致性能断崖式下跌的三个信号
单层游标处理10万行可能耗时几秒,一旦出现嵌套(比如外层查用户,内层查该用户的订单),时间复杂度立刻变成O(n×m),10万用户 × 平均10单 = 100万次FETCH,实际执行可能卡住数分钟甚至超时。
- 监控
SHOW PROCESSLIST,看到状态长期停留在Sending data或Copying to tmp table,基本就是嵌套游标在反复打开/关闭内层游标 - 避免在
WHILE循环体内反复DECLARE + OPEN新游标;改用临时表预存内层结果,再用JOIN或单层游标处理 - 如果内层逻辑只是聚合(如求总金额),优先用子查询或窗口函数替代,而不是游标
游标+事务组合时最容易丢数据的边界情况
在存储过程中边FETCH边COMMIT,看似“每行一提交”,实则隐藏巨大风险:一旦某行处理失败(如API调用超时、下游服务不可用),前面已提交的行无法回滚,业务状态撕裂。
- 不要在循环里写
COMMIT;整个游标块应包在一个事务内,成功则COMMIT,失败则ROLLBACK - 但大事务有锁表风险——折中方案是按固定批次(如500行)分段提交,用计数器控制:
SET @batch_count = @batch_count + 1; IF @batch_count % 500 = 0 THEN COMMIT; START TRANSACTION; END IF; - 记录处理日志表(含
id、status、error_msg、updated_at)比依赖事务更可靠,便于断点续跑
真正棘手的不是语法怎么写,而是确认“非游标不可”——先尝试把逻辑拆成多条UPDATE+INSERT SELECT,再考虑变量计算和IF分支,最后才动游标。只要有一行能被集合操作覆盖,游标就多承担一分不可靠的风险。

















