游标必须在存储过程体内部的begin...end块中声明,且需位于变量声明之后、open之前;其声明、打开、提取、关闭操作须在同一作用域内完成,跨块引用或外部定义均报错。

游标必须在存储过程体内部声明
游标不能在存储过程外部定义,也不能在 BEGIN...END 块之外使用。SQL Server、MySQL 等主流数据库都要求游标声明必须紧挨着 BEGIN,且所有相关操作(OPEN、FETCH、CLOSE、DEALLOCATE)都得在同一个作用域内完成。
常见错误是把 DECLARE cursor_name CURSOR FOR ... 写在存储过程定义语句外,或者试图在多个 BEGIN...END 嵌套块中跨块引用同一游标名——这会直接报错:Must declare the cursor 或 Cursor is not open。
- MySQL 要求游标声明必须在变量声明之后、
OPEN之前,且顺序不能颠倒 - SQL Server 允许在任意位置声明,但实际执行时仍需确保变量已声明且类型匹配
- 所有数据库中,游标名在同一存储过程中必须唯一;重复声明会触发语法错误
FETCH INTO 的变量数量和类型必须严格匹配 SELECT 列表
FETCH NEXT FROM cursor_name INTO @var1, @var2, @var3 这行代码最容易出错。它不是“尽量匹配”,而是硬性要求:变量个数、顺序、数据类型(尤其长度)必须与游标所绑定的 SELECT 结果集完全一致。
例如,若游标定义为 DECLARE cur CURSOR FOR SELECT id, name, created_at FROM users,那么 INTO 后的三个变量就必须是:@id INT、@name VARCHAR(50)、@created_at DATETIME。少一个、多一个、类型不兼容(比如用 INT 接收 TEXT),都会导致运行时报错:Cursorfetch: The number of variables declared in the INTO list must match that of selected columns.
- MySQL 中若列是
TEXT或大字段,对应变量必须声明为TEXT或足够长的VARCHAR,否则截断或报错 - SQL Server 对隐式转换较宽松,但强烈建议显式声明同类型,避免因精度丢失引发业务逻辑错误
- 空值处理:如果查询列可能为
NULL,而变量未设为可空(如@name VARCHAR(50) = NULL),FETCH 可能静默失败
必须用 @@FETCH_STATUS 控制循环,不能依赖计数器
游标本质是状态机,@@FETCH_STATUS(SQL Server)或 NOT FOUND 处理器(MySQL)才是唯一可靠的终止依据。用 ROWCOUNT 或预估行数做 WHILE @i 是危险的——一旦中间行被其他事务删除或锁住,就会跳过、重复或死循环。
正确做法是每次 FETCH 后立刻检查状态:SQL Server 用 WHILE @@FETCH_STATUS = 0;MySQL 必须配合 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; 和 IF done THEN LEAVE loop_label;。
- 注意:
@@FETCH_STATUS是全局变量,会被后续任意FETCH覆盖,不能缓存后复用 - 首次
FETCH就可能失败(比如结果集为空),所以循环体前必须先做一次FETCH,再进WHILE - 不要在循环体内多次
FETCH而不检查状态——漏检会导致处理到无效内存地址,SQL Server 可能抛出Invalid cursor state
CLOSE 和 DEALLOCATE 缺一不可,且顺序不能颠倒
很多开发者只写 CLOSE 就以为释放完毕,结果下次调用存储过程时报错:The cursor is already open 或内存泄漏。实际上,CLOSE 只是断开游标与结果集的连接,DEALLOCATE 才真正释放内存资源和句柄。
两者必须成对出现,且严格按 CLOSE → DEALLOCATE 顺序执行。反过来(先 DEALLOCATE 后 CLOSE)在 SQL Server 中会报错:The cursor does not exist.;MySQL 虽不报错,但再次 OPEN 同名游标会失败。
- 异常路径也要覆盖:建议在
TRY...CATCH(SQL Server)或DECLARE EXIT HANDLER(MySQL)中补上CLOSE和DEALLOCATE - 不要在循环中反复
OPEN/CLOSE/DEALLOCATE游标——这是典型性能陷阱,应一次性打开,逐行处理,最后统一释放 - 游标名在释放后可重用,但同名新游标必须重新
DECLARE,不能跳过声明直接OPEN










