游标是逐行处理的兜底方案而非批量操作替代品,需严格遵循声明顺序、select限制、handler位置、fetch类型匹配及避免循环内耗时操作。

游标不是批量操作的替代品,而是「必须逐行处理」时的兜底方案——用错场景反而拖垮性能。
DECLARE 游标必须放在变量声明之后
MySQL 严格要求游标声明顺序:所有 DECLARE 变量(包括 done 标志)必须在 DECLARE cursor_name CURSOR FOR ... 之前。否则报错 ERROR 1337 (42000): Variable or condition declaration after cursor or handler declaration。
- 错误写法:
DECLARE cur CURSOR FOR SELECT ...; DECLARE done INT DEFAULT FALSE; - 正确顺序:先
DECLARE done INT DEFAULT FALSE;,再DECLARE cur CURSOR FOR SELECT ...; - 游标关联的
SELECT语句不能含动态 SQL(如拼接字符串),也不能是存储过程参数直接嵌入(如WHERE id = in_id是允许的,但WHERE table_name = @tbl不行)
CONTINUE HANDLER FOR NOT FOUND 必须紧随游标声明之后
游标读取到末尾时不会自动退出循环,必须靠 NOT FOUND 处理器设标志位,否则 FETCH 会持续失败并最终报错 ERROR 1329 (02000): No data - zero rows fetched, selected, or processed。
- 处理器声明位置很关键:必须在游标声明后、
OPEN前,且不能在其他DECLARE中间穿插 - 典型写法:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; - 注意:
done初始值必须是FALSE(或0),否则循环一次就跳出了
FETCH INTO 的变量类型和顺序必须与 SELECT 完全一致
字段数量、类型、顺序错一位都会导致赋值失败或截断,且 MySQL 不报类型不匹配警告,只静默出错(比如 VARCHAR(10) 接收超长值会被截断,INT 接收 NULL 可能转成 0)。
- 示例:游标
DECLARE cur CURSOR FOR SELECT name, salary FROM employees;→FETCH cur INTO @emp_name, @emp_salary; - 如果
salary是DECIMAL(10,2),而@emp_salary声明为INT,小数部分丢失 - 若 SELECT 返回 3 列但只
INTO2 个变量,第三列数据直接丢弃,无提示
WHILE 循环里不要放耗时操作
游标本质是单行驱动,每循环一次都是一次逻辑执行单元。把 INSERT、UPDATE、远程 API 调用塞进循环体,性能会指数级恶化。
- 常见反模式:在
LOOP内对每一行执行UPDATE users SET status = 'processed' WHERE id = @id; - 更优解:用
INSERT ... SELECT或UPDATE ... JOIN替代;实在要逐行调外部服务,至少加批处理缓冲(如攒够 10 行再发一次请求) - 游标本身不支持
OFFSET/LIMIT分页,大数据量时务必配合WHERE条件缩小结果集,否则OPEN阶段就卡住
真正需要游标的地方很少——多数所谓“逐行处理”其实都能用集合操作重写。别为了写游标而写游标,先问自己一句:这个逻辑,能不能用一条 UPDATE 或 INSERT SELECT 干完?











