SQL Server 2022 中不存在“JOIN 溢出”事件,真正反映内存溢出的是执行计划 XML 中的 <Warnings><SpillToTempDb> 或 <HashWarning> 节点,需通过 query_post_execution_showplan 事件捕获并结合 sys.dm_db_task_space_usage 等 DMV 验证。

SQL Server 2022 中没有名为 “JOIN 溢出” 的独立事件,spill_to_tempdb、sort_warning、hash_warning 这些才是真实触发内存溢出的信号——它们藏在执行计划 XML 的 <warnings></warnings> 节点里,不是靠扩展事件“监听 JOIN”就能抓到的。
别指望 sqlserver.join_completed 这类事件
SQL Server 根本不提供 join_completed、hash_join_spill 或任何以 JOIN 命名的扩展事件。你搜不到,是因为它不存在。试图用这类名字建会话只会捕获零记录。真正暴露 JOIN 内存问题的,是它引发的副作用:
-
sql_statement_completed中logical_reads > 10000或duration > 5000000(5 秒),说明查询整体被拖垮 -
query_post_execution_showplan返回的 XML 里含<Warnings><SpillToTempDb>或<HashWarning>节点 -
wait_info里高频出现cxpacket(并行失衡)或pageiolatch_sh(临时表读写抖动)
必须开启 query_post_execution_showplan 并过滤
这个事件是唯一能自动带出警告详情的入口,但它开销高,不能无条件启用:
- 务必加 WHERE 条件:
[duration] > 1000000(1 秒以上才抓),否则每条语句都序列化整个执行计划,实例直接卡死 - 目标选
ring_buffer(内存中)做快速诊断;生产环境长期运行请改用event_file并配合轮转归档 - ACTION 必须包含
sqlserver.sql_text和sqlserver.plan_handle,否则你看到的只是“某条语句慢”,看不到具体 SQL 和警告位置 - Azure SQL 不支持该事件,只能退回到
sql_statement_completed+ 手工查sys.dm_exec_query_plan(plan_handle)
从执行计划 XML 里直接定位 Spill 警告
拿到 query_post_execution_showplan 输出后,不要只看图形执行计划——图形界面常把警告折叠或隐藏。打开 XML,搜索以下节点:
-
<Warnings><SpillToTempDb>:表示 HASH JOIN 或 SORT 溢出到 tempdb,对应 ERROR 701 风险 -
<Warnings><NoJoinPredicate>:ON 条件缺失或被优化器忽略,JOIN 变成交叉连接(CROSS JOIN),结果集爆炸 -
<Adaptive>1</Adaptive>:确认是否启用了自适应连接,这是 SQL Server 2022 中 HASH JOIN 突然吃光内存的主因
注意:SpillToTempDb 出现在哪个算子下(比如 <RelOp LogicalOp="Hash Match">),就说明那个 JOIN 正在溢出——不是所有 HASH JOIN 都会溢出,只看实际执行时的 ActualRows 与 EstimatedRows 是否偏差超 5 倍。
用 DMV 辅助验证 tempdb 溢出痕迹
扩展事件抓到警告后,还需确认 tempdb 是否真被拖累:
- 查
sys.dm_db_task_space_usage,看session_id对应的user_objects_alloc_page_count是否异常高(比如单次查询分配上万页) - 查
sys.dm_exec_requests中wait_type = 'PAGEIOLATCH_SH'且wait_resource包含2:1:(tempdb 文件 ID 为 2),说明正在疯狂读 tempdb 里的溢出数据 - 避免只盯
sys.dm_os_wait_stats里的总等待时间——它掩盖了单次严重溢出,要结合请求级实时视图
最易被忽略的一点:溢出警告可能只在首次执行时出现,后续因计划缓存复用而消失;所以监控必须覆盖首次编译和强制重编译场景,不能只等“慢查询报警”才动手。

















