应直接查看执行计划中的warning节点,而非依赖扩展事件监听join本身;重点捕获sql_statement_completed事件(过滤logical_reads>10000或duration>5秒)和query_post_execution_showplan事件(获取含warnings的xml执行计划),并结合wait_info分析cxpacket、sort_warning等关联等待。

直接看执行计划里的 Warning,不是靠扩展事件抓 JOIN 本身
扩展事件不提供“某个 JOIN 很慢”的直接事件。SQL Server 不会在执行时单独抛出 join_slow 或 bad_left_join 这类事件。真正能暴露 JOIN 低效的,是它引发的副作用:高逻辑读、长执行时间、内存溢出、哈希/排序警告。所以重点不是监听 JOIN 语法,而是监听那些被 JOIN 拖垮的查询行为。
用 sql_statement_completed + duration 和 logical_reads 筛选可疑查询
这是最常用也最有效的入口。JOIN 效率低下通常表现为单次执行逻辑读高(比如 >10000)或耗时长(比如 >5 秒),尤其在 WHERE 或 ON 条件没走索引时。
-
sql_statement_completed是必选事件,它带出真实执行开销指标 - 必须在
WHERE子句中加条件过滤,否则会捕获全部语句,噪音极大 - 推荐组合条件:
[logical_reads] > 10000 OR [duration] > 5000000(单位是微秒) - 务必添加
ACTION:sqlserver.sql_text和sqlserver.plan_handle,否则你只能看到“某条语句慢”,看不到具体 SQL 和执行计划 - 避免只用
sql_batch_completed,它粒度太粗,无法定位到具体 JOIN 所在的语句块
别漏掉 query_post_execution_showplan —— 它能自动附带警告信息
这个事件会在每次查询执行完后返回实际执行计划 XML,关键在于:如果优化器检测到 JOIN 引发了严重问题(例如缺失统计信息、强制序列化、临时哈希表溢出到磁盘),它会在 XML 的 <warnings></warnings> 节点里直接标记,比如 MissingIndex、SpillToTempDb、NoJoinPredicate。
- 开启它代价略高(需序列化整个执行计划),但对排查 JOIN 问题不可替代
- 必须配合
WHERE [duration] > 1000000之类条件,否则开销不可控 - 输出目标建议用
ring_buffer(内存中)做短期诊断,或event_file配合定期归档 - 注意:Azure SQL 数据库不支持该事件,只能退回到
sql_statement_completed+ 后续手工查sys.dm_exec_query_plan
警惕 wait_info 里暴露的 JOIN 隐性瓶颈
某些 JOIN 场景不会立刻变慢,但会让线程卡在特定等待上。比如:
-
cxpacket:并行 JOIN 分配不均,常见于 LEFT JOIN 驱动表过大 + 并行度设置过高 -
sort_warning:MERGE JOIN 或窗口函数需要排序但内存不足 -
pageiolatch_sh或write_log:哈希 JOIN 写溢出文件或日志压力大 - 用
wait_info事件配合sqlserver.session_id动作,能把等待和具体 SQL 关联起来 - 不要单独启用
wait_info,它每毫秒都可能触发,必须加谓词:[wait_type] IN (N'cxpacket', N'sort_warning', N'pageiolatch_sh')
真正难的不是建会话,而是从捕获结果里快速识别哪一行 JOIN 是罪魁祸首——它往往藏在嵌套子查询里、动态拼接中、或被视图/函数封装。拿到 sql_text 后,第一件事永远是粘进 SSMS,按 CTRL+L 看执行计划,盯住那个带黄色感叹号的 JOIN 运算符。











