SQL Server存储过程中必须CLOSE+DEALLOCATE游标:CLOSE仅释放结果集和位置锁,保留游标结构;DEALLOCATE才销毁对象、释放内存和句柄,二者语义分离、缺一不可,否则会触发Msg 16915等错误。

SQL Server 存储过程中必须 CLOSE + DEALLOCATE 游标
只写 CLOSE cursor_name 不够,只写 DEALLOCATE cursor_name 也不行——二者语义完全不同,缺一不可。CLOSE 仅释放结果集和游标位置锁,保留游标定义结构;DEALLOCATE 才真正销毁对象、释放内存和系统句柄。漏掉任意一步,下次执行存储过程时大概率报错:Msg 16915, Level 16, State 1: A cursor with the name 'xxx' already exists.
常见踩坑点:
- 在事务中执行
COMMIT后游标自动关闭(因默认CURSOR_CLOSE_ON_COMMIT = ON),但未DEALLOCATE,导致后续OPEN失败 - 循环调用同一存储过程时,上一轮的游标名仍注册在会话中,
DECLARE直接失败 - 长时间运行后
sys.dm_exec_sessions显示大量 sleeping 会话,内存占用持续升高
MySQL 中 PREPARE/EXECUTE 必须配对 DEALLOCATE PREPARE
MySQL 没有 DEALLOCATE cursor_name,但对预处理语句(PREPARE)同样严格:每个 PREPARE stmt_name FROM @sql 都会占用一个会话级句柄,不显式 DEALLOCATE PREPARE stmt_name 就会累积,最终触发 ERROR 1437: Out of memory; check if you need to increase the max_prepared_stmt_count value。
注意:
-
DEALLOCATE PREPARE不是“可选优化”,是资源管理刚需 - 不能靠连接断开自动清理——长连接场景下句柄会一直堆积
- 若在存储过程中动态生成并执行多条 SQL,每条都需独立
PREPARE+DEALLOCATE PREPARE,不能复用同一stmt_name而跳过释放
Oracle REF CURSOR 不关不释放,且无自动回收
Oracle 的 SYS_REFCURSOR 是服务器端游标,一旦 OPEN 就分配 PGA 内存,客户端不显式 CLOSE(或 PL/SQL 块退出未 CLOSE),该内存不会释放,长期运行会导致 PGA 占用飙升,甚至触发 ORA-04030: out of process memory。
关键事实:
- REF CURSOR 没有类似 SQL Server 的
DEALLOCATE,CLOSE即完成全部释放 - 但必须由打开者关闭——不能依赖调用方(如 Java 的
ResultSet.close())来兜底,因为 T-SQL 层已失去控制权 - 嵌套过程调用时,若父过程
OPEN后传给子过程,子过程必须负责CLOSE,否则父过程无法感知是否已关
ODBC 层资源泄漏和存储过程无关,但常被误判
所谓“存储过程没关连接”是典型归因错误。SQL Server 存储过程运行在已有会话内,它无法主动关闭客户端发起的连接。真正泄漏的是 ODBC 或 .NET 的句柄,比如:
- 客户端调用
SQLAllocHandle(SQL_HANDLE_STMT, ...)后没配对SQLFreeHandle(SQL_HANDLE_STMT, ...) - C# 里用
SqlCommand但没包在using块中,也忘了手动connection.Close() - 异步执行时
ExecuteReaderAsync没await就 return,导致连接对象延迟 GC
这类问题在监控中表现为 sys.dm_exec_sessions 里大量 sleeping 状态连接,但查存储过程代码永远找不到“关连接”的位置——因为根本不在那儿。











