SQL Server存储过程中内存飙升主因是游标未CLOSE/DEALLOCATE、临时表未DROP及结果集未截断;游标需成对清理,临时表应显式删除,SET NOCOUNT ON可降低执行缓存开销,须用DMV验证资源是否真实释放。

SQL Server 存储过程中内存飙升,往往不是因为数据量大,而是游标没关、临时表没删、结果集没截断——这些资源在执行完后仍被锁在内存里,尤其在高并发调用时会快速堆积。
为什么游标不显式关闭会导致内存持续占用
游标(DECLARE cursor_name CURSOR)在 SQL Server 中属于“会话级资源”,即使 FETCH 完毕,只要没执行 CLOSE 和 DEALLOCATE,其元数据、工作表、排序缓冲区就一直驻留在 tempdb 和内存中。更隐蔽的是:如果游标在 TRY...CATCH 块中抛出异常,而 CLOSE/DEALLOCATE 写在 END TRY 后面,那它根本不会被执行。
- 必须成对使用:
CLOSE cursor_name+DEALLOCATE cursor_name;只CLOSE不DEALLOCATE仍占内存 - 所有游标操作建议包在
TRY...CATCH的FINALLY等效逻辑里(即END CATCH后补清理语句) - 避免在循环中反复
DECLARE同名游标(某些版本会报错,或导致计划缓存污染)
临时表不显式删除的内存陷阱
#temp_table 在会话结束时自动释放,但它的数据页、统计信息、索引结构在生命周期内全程占用 tempdb 数据缓存和内存。若存储过程被多次嵌套调用(如 A → B → C),每个层级都建同名临时表,实际会生成多个物理对象,且不会互相覆盖。
- 显式执行
DROP TABLE #temp_table,不要依赖会话退出自动清理 - 避免用
SELECT * INTO #t FROM ...创建无索引临时表——大数据量时排序/哈希操作会吃光work_mem类似资源 - 若只需轻量暂存,优先用表变量(
DECLARE @t TABLE(...)),它不走tempdb日志,内存开销更低(但行数超万时性能反降)
SET NOCOUNT ON 不只是减少网络流量
很多人知道 SET NOCOUNT ON 能抑制 “(1 row(s) affected” 消息,却忽略它对内存的影响:当存储过程含大量小更新(如逐行 UPDATE 或循环 INSERT),每条语句返回影响行数消息会触发额外的计划缓存条目生成和结果集元数据构造,间接推高执行缓存(sys.dm_exec_cached_plans)占用。
- 在存储过程开头统一加
SET NOCOUNT ON,结尾无需OFF - 尤其在含游标循环或批处理的场景下,实测可降低执行缓存体积 30% 以上(参考
size_in_bytes字段) - ORM(如 EF Core)调用存储过程时,该设置还能避免客户端驱动误解析影响行数为错误
如何验证游标与临时表是否已释放
不能只看存储过程是否“跑完了”,要查真实资源残留。以下两个 DMV 是关键:
SELECT session_id, cursor_id, name, creation_time FROM sys.dm_exec_cursors(0) WHERE is_open = 1;
SELECT t.name, t.create_date, s.session_id FROM tempdb.sys.tables t JOIN sys.dm_exec_sessions s ON t.create_date > s.last_request_end_time WHERE t.name LIKE '#%';
前者查所有打开状态的游标(is_open = 1 即未 CLOSE);后者结合 create_date 和会话最后请求时间,能揪出那些“本该随会话结束但因异常卡住”的临时表。这两类对象一旦存在,就说明清理逻辑有漏点。
最易被忽略的是:在事务中打开游标后发生回滚(ROLLBACK),游标状态不会自动重置,CLOSE/DEALLOCATE 仍需手动执行——这点和锁不同,必须写进代码,不能指望数据库替你兜底。

















