mysql游标必须配not found处理器,否则fetch失败后while循环不会自动退出;需declare done int default false并紧跟continue handler,禁用@@fetch_status主判断,update须显式用主键条件,避免嵌套游标,优先用集合操作替代。

MySQL游标必须配NOT FOUND处理器,否则循环失控
不加错误处理器的游标在FETCH失败后不会自动退出,WHILE @@FETCH_STATUS = 0会卡死或跳过最后一行。这不是版本兼容问题,而是MySQL游标机制决定的——它没有内置EOF自动感知。
-
DECLARE done INT DEFAULT FALSE必须在DECLARE cursor_name CURSOR FOR ...之后、OPEN之前声明 - 紧接着写:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE - 如果游标查询可能返回空集,
done初始值不能是TRUE,否则WHILE NOT done直接跳过整个循环 - 别依赖
@@FETCH_STATUS做主判断:它在子查询嵌套或事务中行为不稳定,仅作辅助调试用
UPDATE不能用WHERE CURRENT OF,必须显式带主键条件
MySQL根本不支持WHERE CURRENT OF cursor_name语法,照搬SQL Server或Oracle写法会直接报错:ERROR 1325 (HY000): Cursor is not open 或 Unknown column 'cursor_name' in 'where clause'。
- SELECT语句里必须包含主键或唯一标识字段,例如:
SELECT id, name, status FROM users WHERE status = 'pending' - FETCH时把主键存进变量:
FETCH cur_user INTO @user_id, @user_name, @user_status - UPDATE时显式用该变量:
UPDATE users SET status = 'processed' WHERE id = @user_id - 高并发下加幂等条件:
WHERE id = @user_id AND status = 'pending',再检查ROW_COUNT()是否为1,避免重复处理
嵌套游标会让性能断崖式下跌,优先用临时表+JOIN替代
外层10万行 × 内层平均10行 = 100万次FETCH,实际执行时间可能从秒级涨到分钟级,且SHOW PROCESSLIST里会长期卡在Sending data或Copying to tmp table状态。
- 禁止在
WHILE循环体内反复DECLARE + OPEN新游标 - 改用
SELECT ... INTO #temp_outer固化外层ID集,防止基表变更干扰 - 内层数据批量拉取:
INSERT INTO #temp_inner SELECT * FROM orders WHERE user_id IN (SELECT id FROM #temp_outer) - 转换逻辑抽成标量函数(如
dbo.fn_score_level),在UPDATE中统一调用,避免每行重复解析CASE或CAST
游标只是最后手段,集合操作能解决的就别碰游标
只要业务逻辑不涉及跨库调用、动态SQL拼接、上一行结果决定下一行操作、或循环中需精细控制事务边界,就一定有比游标更优的方案。
- 批量更新优先用
UPDATE ... JOIN或MERGE(MySQL 8.0+) - 状态转换类逻辑封装成函数,让SQL本身承担计算,而不是在游标循环里
SET @level = CASE ... - 日志记录类需求,用触发器或应用层异步落库,避免游标里
INSERT INTO audit_log拖慢主流程 - 真正需要游标时,只让它干一件事:调度。所有计算、转换、校验都提前做完或交给函数
游标最危险的不是写错语法,而是把它当成“可控的for循环”来用——而数据库的执行模型根本不是为逐行控制设计的。越早意识到这点,越少掉进锁等待、隐式转换、计划缓存失效这些深坑。











