declare cursor需显式open才能取数,否则fetch返回-1;默认forward_only+read_only且不自动打开,漏open则游标无效;正确循环结构是open后先fetch再while@@fetch_status=0处理。

SQL Server 里 DECLARE CURSOR 为什么总卡住或查不到数据
游标不是“查完就走”的工具,它默认是 FORWARD_ONLY + READ_ONLY,且不自动打开。常见现象是执行完 DECLARE CURSOR 没报错,但 FETCH 返回空或 @@FETCH_STATUS = -1 —— 实际上根本没真正读到第一行。
- 必须显式执行
OPEN cursor_name才能开始取数,漏掉这步等于声明了个摆设 - 如果
SELECT子句里用了未提交的事务数据(比如在另一个会话里INSERT但没COMMIT),而当前游标隔离级别是默认的READ COMMITTED,就会跳过那些“脏”行,看起来像少数据 - 游标作用域只在当前批处理内,不能跨
EXEC或存储过程调用边界传递;想复用得用全局游标(加GLOBAL关键字),但会增加锁竞争风险
用 WHILE @@FETCH_STATUS = 0 循环时,为什么老多跑一次
典型错误是把 FETCH 放在循环末尾,导致最后一次成功取值后仍进入循环体一次,再 FETCH 失败才退出。结果就是:本该处理 N 行,却执行了 N+1 次循环体,最后那次操作基于旧变量值,容易出逻辑错。
- 正确结构是:先
OPEN,再FETCH第一行,然后用WHILE @@FETCH_STATUS = 0包裹处理逻辑和下一次FETCH -
@@FETCH_STATUS是会话级状态,只要中间有别的游标操作(比如嵌套游标、或调用含游标的存储过程),它的值可能被覆盖,别依赖它跨多层逻辑 - 如果循环里要修改当前行对应的数据,必须用
FOR UPDATE声明游标,并在UPDATE ... WHERE CURRENT OF cursor_name中使用,否则报错
替代方案比游标更稳的三个场景
游标本质是逐行驱动,SQL Server 对集合操作做了大量优化,大多数时候用集合写法更安全、更快,也更容易调试。
- 需要按顺序更新某列(比如生成序号):用
ROW_NUMBER()窗口函数 + CTE,避免游标锁表时间过长 - 要对每行做简单计算并插入新表:直接
INSERT INTO ... SELECT ...,比开游标再循环INSERT快一个数量级 - 需根据前一行结果决定当前行逻辑(如累计求和):用
SUM() OVER (ORDER BY ...),而不是靠游标变量暂存上一行值
游标性能差不只是因为“慢”,还因为锁行为难预测
游标不是单纯“慢”,它在不同选项下锁行为差异极大,容易引发阻塞甚至死锁,尤其在高并发写入场景下。
-
STATIC游标会把结果集快照复制到 tempdb,内存/磁盘开销大,且看不到其他会话的后续变更 -
KEYSET只缓存键值,支持看到新插入行(若符合 WHERE 条件),但删除行后FETCH会返回@@FETCH_STATUS = -2,需额外判断 - 默认
READ_COMMITTED隔离下,每取一行都可能触发新锁,而集合操作通常是一次性加锁,持续时间短得多
真要用游标,优先选 LOCAL FAST_FORWARD READ_ONLY,这是最轻量、最接近只读遍历的组合;但凡涉及更新、跨事务或大数据量,先想想能不能绕开它。










