@@FETCH_STATUS是SQL Server中用于返回最后一条FETCH语句执行状态的全局整数变量,值为0表示成功,-1表示失败或到结果集末尾,-2表示行不存在,-9表示未执行提取操作;因其全局性且不抛异常,必须在每次FETCH后立即检查,不可仅依赖WHILE条件判断。

游标不是“能用就用”的工具,而是“别无选择时才启用”的兜底方案。绝大多数逐行需求都能改写成集合操作,硬上游标只会拖慢执行、锁表、还容易漏关资源。
为什么FETCH后必须立刻检查@@FETCH_STATUS或NOT FOUND
游标取数失败不会抛异常,而是静默设置状态变量。如果把状态判断塞进WHILE条件里,第一行就为空(比如SELECT没命中任何记录),FETCH返回-1,循环直接跳过,整个逻辑就失效了。
- SQL Server 必须在
FETCH NEXT后紧跟着IF @@FETCH_STATUS != 0 BREAK,不能只靠WHILE @@FETCH_STATUS = 0 - MySQL 必须提前声明
CONTINUE HANDLER FOR NOT FOUND SET done = TRUE,且FETCH后立刻IF done THEN LEAVE loop_name; - Oracle 的
%NOTFOUND属性也是同理:要在FETCH之后立即判断,不能延迟到下一次循环开头
DECLARE CURSOR时哪些参数会悄悄拖垮性能
游标类型选错,轻则多占内存,重则锁死整张表。尤其在高并发环境,一个SCROLL或KEYSET游标可能让tempdb爆满或阻塞其他事务。
- 校验/只读场景一律用
LOCAL FAST_FORWARD(SQL Server)或READ ONLY(MySQL),它隐含FORWARD_ONLY+READ_ONLY,开销最小 - 绝对避免
STATIC:它会把整个结果集拷贝到tempdb,10万行数据可能瞬间吃光空间 - 别用
SCROLL除非真要FETCH PRIOR或FETCH ABSOLUTE 5——99%的业务逻辑根本不需要回溯
变量绑定不严会导致数据截断或报错
FETCH INTO不是赋值语句,而是内存映射。变量数量、顺序、类型、长度必须和SELECT字段严格对齐,否则要么报错,要么静默丢数据。
- 少声明一个变量?
INTO直接报错Column count doesn't match variable count -
@email VARCHAR(50)但实际值长62字符?MySQL会截断不提示,SQL Server可能报错或静默截断,取决于ANSI_WARNINGS设置 - 字段是
DECIMAL(12,4),变量却声明为DECIMAL(10,2)?小数位丢失,手续费算错就是这么来的
资源没释放干净的后果比你想象的严重
游标不CLOSE不DEALLOCATE(SQL Server)或不CLOSE(MySQL),下次调用存储过程时可能复用旧游标句柄,导致数据错乱;长期运行的服务还会累积句柄泄漏,最终触发Maximum cursors exceeded错误。
- 必须把
CLOSE和DEALLOCATE(或CLOSE)写在END前,且放在TRY...CATCH的FINALLY位置(SQL Server)或EXIT HANDLER里(MySQL) - 不要在循环里反复
OPEN同一个游标——每次OPEN都重跑一遍SELECT,IO和CPU双浪费 - MySQL 存储过程中,
DECLARE CONTINUE HANDLER只能捕获当前作用域的NOT FOUND,别指望外层HANDLER能兜住内层游标异常
最常被忽略的一点:游标本身不提供事务边界。如果你在循环里做UPDATE,每一行都是独立提交还是整个存储过程共用一个事务,完全取决于你有没有显式BEGIN TRAN和COMMIT——而多数人默认以为“存储过程天然原子”,结果发现部分行更新成功、部分失败,数据就残了。

















