Hash Match 吃光 TempDB 空间主因是估算行数严重偏低导致哈希表溢出到磁盘,常见于统计信息过期、无索引连接列、未下推过滤条件;可通过执行计划 SpillLevel > 0 或监控 sys.dm_db_session_space_usage 判断;解决需补索引、下推 WHERE、建临时表加索引、合理配置 tempdb 文件数量与增长。

Hash Match 为什么吃光 TempDB 空间
SQL Server 在执行大表 JOIN 时,若优化器选择 Hash Match 算法,就会在 tempdb 中构建哈希表——这个哈希表不是只存几行数据,而是按估算行数分配内存页;一旦估算严重偏低(比如统计信息过期、CTE 或视图隐藏了真实基数),实际数据远超预估,SQL Server 就会把哈希表“溢出”到 tempdb 数据文件中做磁盘哈希(spill to tempdb)。此时你看到的不是慢,而是 Could not allocate space for object 'dbo.#hash_table' in database 'tempdb'。
常见触发场景:
- 两个百万级以上表做
INNER JOIN,连接列无索引,优化器放弃Merge Join和Nested Loops,强行选Hash Match - JOIN 前没加
WHERE过滤,导致参与哈希构建的行数翻倍(例如先 JOIN 再WHERE status = 1) - 查询含
GROUP BY或ORDER BY,又没走索引排序,额外触发tempdb排序区
怎么判断当前 Hash Match 是否已 spill
看执行计划 XML 或 SSMS 图形化计划里,Hash Match 算子属性中是否有 SpillLevel > 0 或提示 Warning: Operator used tempdb。更直接的方法是运行时监控:
SELECT session_id, request_id, operation_type,
internal_objects_alloc_page_count,
user_objects_alloc_page_count
FROM sys.dm_db_session_space_usage
WHERE internal_objects_alloc_page_count > 0;如果某 session 的 internal_objects_alloc_page_count 持续飙升,基本就是它在往 tempdb 里灌哈希溢出页。
注意:sys.dm_exec_query_plan 查不到 spill 详情,必须结合 sys.dm_exec_query_stats + query_plan_hash 关联历史计划,或开启 QUERYTRACEON 3604, 3605 抓后台日志(生产慎用)。
避免 Hash Match spill 的实操手段
核心思路不是“禁用 Hash Match”,而是让优化器有更好选择,或控制其资源消耗边界:
- 给连接列补上非聚集索引:哪怕只是
CREATE INDEX IX_orders_user_id ON orders(user_id),就可能让优化器转向Nested Loops(尤其当 users 表很小) - 强制下推过滤:把
WHERE条件写进子查询,而不是留在外层,例如JOIN (SELECT * FROM orders WHERE order_date >= '2025-01-01') o ON ... - 对中间结果显式建临时表并加索引:比 CTE 更可控,
SELECT ... INTO #orders_2025 FROM orders WHERE ...; CREATE INDEX IX_#o_uid ON #orders_2025(user_id); - 用
OPTION (HASH JOIN, FAST 100)引导优化器优先尝试哈希,但配合FAST提前返回小结果集,减少整体内存压力 - 在 SQL Server 2025+ 上,为该查询绑定工作负荷组,并设置
GROUP_MAX_TEMPDB_DATA_MB = 2048,让 spill 到阈值就报错中断,避免拖垮整个实例
TempDB 文件配置对 Hash Match 的实际影响
即使你做了所有 SQL 层优化,tempdb 本身设计不合理,Hash Match spill 仍会卡住或变慢:
- 单个
tempdb数据文件 + 高并发查询 → 所有哈希溢出争抢同一个 PFS/GAM 页,出现PAGEIOLATCH_UP等待 - 文件自动增长设成 64MB(默认值)→ 每次 spill 触发增长,IO 卡顿叠加内存重分配,雪崩式延迟
- 多个 CPU 核心但只配 1 个
tempdb文件 → 无法并行写入溢出页,哈希构建被迫串行化
正确做法是:CPU 核数 ≤ 8 时配 4 个等大小 tempdb 数据文件;>8 时配 8 个;每个初始大小 ≥2GB,FILEGROWTH 设为 204800KB(约 200MB),且关闭自动增长(改由监控告警+人工扩容)。
真正容易被忽略的是:Hash Match spill 不仅占空间,还锁住 tempdb 的系统页结构;一次 spill 失败后,后续查询可能因等待 PFS 更新而集体 hang 住,连 KILL 都难生效。

















