根本原因是快照隔离下每行旧值保留版本记录且中间操作写入tempdb,事务未提交则空间不释放;需检查并关闭不必要的READ_COMMITTED_SNAPSHOT,避免排序哈希溢出,拆分大更新,杜绝悬空事务。

大事务UPDATE为什么撑爆Tempdb?
根本原因不是“改得多”,而是SQL Server在快照隔离下为每一行旧值保留版本记录,同时大范围扫描、排序、哈希等操作又会把中间结果写入tempdb。只要事务没提交,这些空间就无法释放——哪怕你只UPDATE了100万行,且WHERE没走索引,tempdb可能瞬间涨几个GB。
检查是否误开了快照隔离
快照隔离是默认不开启的,但很多DBA为解决阻塞问题会打开READ_COMMITTED_SNAPSHOT。一旦开启,所有UPDATE都会生成行版本,而大事务会让版本链堆积在tempdb中。
- 运行
SELECT is_read_committed_snapshot_on, snapshot_isolation_state FROM sys.databases WHERE name = 'your_db'确认状态 - 若
is_read_committed_snapshot_on = 1且业务不需要快照语义(比如没用SNAPSHOT事务隔离级别),建议关闭:ALTER DATABASE your_db SET READ_COMMITTED_SNAPSHOT OFF - 注意:该操作不中断现有连接,但新连接才生效;已有长事务仍会继续占用版本空间
让UPDATE避开排序和哈希溢出
即使没开快照,UPDATE带函数、类型转换或更新列上有计算列/XML索引时,SQL Server仍可能被迫在tempdb里做Sort或Hash Match——执行计划里出现红色警告“Operator used tempdb”就是信号。
- 避免在
WHERE子句中对字段用函数:WHERE UPPER(name) = 'ABC'→ 改为WHERE name = 'abc'并确保列大小写敏感设置匹配 - 检查执行计划XML,搜索
<relop.></relop.>或<relop. match></relop.> - 对WHERE条件字段建索引,且确保统计信息新鲜:
UPDATE STATISTICS your_table WITH FULLSCAN - 大表批量更新务必拆批:
UPDATE TOP (5000) t SET status = 'done' FROM orders t WHERE status = 'pending',循环执行直到影响行为0
别让事务悬空在那儿
最隐蔽也最致命的场景:BEGIN TRAN后UPDATE没COMMIT/ROLLBACK,或者存储过程中异常跳过提交路径。此时sys.dm_tran_active_snapshot_database_transactions里elapsed_time_seconds可能高达数小时,tempdb空间被死锁住,DBCC SHRINKFILE完全无效。
- 查长事务:
SELECT session_id, elapsed_time_seconds FROM sys.dm_tran_active_snapshot_database_transactions WHERE elapsed_time_seconds > 300 - 关联会话查SQL:
SELECT r.session_id, t.text FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.session_id = xxx - 存储过程里所有分支都必须有明确事务结束逻辑,CATCH块里不能只RETURN,要加
IF XACT_STATE() 0 ROLLBACK
真正难防的不是单次巨量UPDATE,而是那些每天跑一次、看似稳定、却因统计信息陈旧或内存估算偏差而悄悄spill的语句——它们不会报错,但tempdb会持续缓慢膨胀,直到某天突然填满。

















