sql server关联查询本身不直接撑爆tempdb,真正导致暴涨的是执行计划中隐含的排序(sort)和哈希(hash join/aggregate)操作——当数据量超出内存配额时,sql server会将中间结果写入tempdb;可通过sys.dm_db_session_space_usage查internal_objects_alloc_page_count定位高占用会话,并结合sys.dm_exec_requests与执行计划xml确认sort或hash match节点及内存授予不足问题。

SQL Server 关联查询本身不会直接撑爆 tempdb,真正触发空间暴涨的是执行计划中隐含的**排序(Sort)** 和 **哈希(Hash Join / Hash Aggregate)** 操作——尤其当数据量超出内存(max server memory 或会话级 query_memory_grant)时,SQL Server 会把中间结果批量写入 tempdb。这不是 bug,是设计行为;但没被监控或预估,就会突然报错 数据库 'tempdb' 的日志已满 或 无法为会话分配页。
怎么确认是排序或哈希在吃掉 tempdb?
别猜,直接查运行中的空间分配来源:
- 用
sys.dm_db_session_space_usage找出当前占用tempdb最多的会话:SELECT session_id, user_objects_alloc_page_count, internal_objects_alloc_page_count FROM sys.dm_db_session_space_usage WHERE internal_objects_alloc_page_count > 100000;重点关注internal_objects_alloc_page_count,它代表排序、哈希、游标等内部操作分配的页数 - 结合
sys.dm_exec_requests和sys.dm_exec_sql_text把会话 ID 对应回 SQL 语句:SELECT r.session_id, t.text, r.wait_type, r.wait_time FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.session_id IN (/* 上一步查出的高占用 session_id */);
如果wait_type是sort、exchange或pageiolatch_*,基本坐实 - 看执行计划 XML 中是否有
<relop nodeid="*" physicalop="Sort"></relop>或<relop nodeid="*" physicalop="Hash Match"></relop>节点——注意不是所有 Hash Join 都溢出,要看EstimateRows和EstimatedAvailableMemoryKB是否严重不匹配
为什么加了索引还是走哈希?
索引能避免排序,但对哈希连接没直接帮助。SQL Server 选哈希还是嵌套循环(Nested Loops),取决于三件事:
-
JOIN列是否在索引键最左列(覆盖才有用),且统计信息是否过期——过期会导致行数估算偏差,让优化器误判“小表”实际很大,强行选哈希 - 内存授予(
granted_memory_kb)不足:即使有足够物理内存,SQL Server 也会按查询估算值预留,若估算太低(比如没更新统计信息),实际需要更多内存时就只能往tempdb写 - 并行度设置过高:多个线程同时做哈希,每个线程都申请独立内存块,总需求翻倍,更容易溢出
验证方式:SET STATISTICS XML ON 后执行 SQL,看执行计划里 GrantedMemory 和 UsedMemory 差距;差几百 MB 就大概率溢出了。
临时缓解:不改 SQL 怎么快速释放空间?
紧急情况下,不能停业务,但可以阻断持续增长:
- 立刻 kill 掉已知的罪魁会话:
KILL <session_id></session_id>—— 注意别杀系统会话(session_id ) - 收缩
tempdb日志文件(仅限简单恢复模式下有效):DBCC SHRINKFILE ('templog', 256);数字单位是 MB,别设太小(低于 256MB 容易反复增长) - 禁止新大查询进 tempdb(治标):
ALTER DATABASE tempdb SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE;,处理完再切回MULTI_USER
这些操作不解决根本问题,但能抢出 10–20 分钟窗口去查慢查询、更新统计信息或加索引。
长期规避:从执行计划源头压住 tempdb 用量
关键不是“不让用 tempdb”,而是“让它尽量少用”:
- 对
ORDER BY、GROUP BY、DISTINCT字段建**覆盖索引**,避免 Sort 溢出;例如CREATE INDEX IX_T1_ColA_Sort ON T1(ColA) INCLUDE (ColB, ColC); - 对 JOIN 字段确保两边都有索引,且类型一致(避免隐式转换导致索引失效);用
CONVERT显式转,比让优化器猜安全 - 定期更新统计信息:
UPDATE STATISTICS [schema].[table] WITH FULLSCAN;,尤其在大批量导入后 - 限制单次查询返回行数:
TOP (5000)或分页(OFFSET ... FETCH),防止前端没加 limit 导致全表哈希
最常被忽略的一点:很多“大关联”其实只需要最新 N 条数据,但开发写了 SELECT * FROM A JOIN B ON ... 然后在应用层过滤。这种逻辑必须下沉到 SQL 层,否则 tempdb 一定撑不住。










