sql server 2022的自适应join是运行时动态决策,根据驱动表实际行数是否超过adaptivethresholdrows阈值,在nested loops join与hash join间切换,需通过实际执行计划(ctrl+m)及xml中adaptivejointype、actualrows等字段确认真实路径。

SQL Server 2022的自适应JOIN不是静态计划,而是运行时决策
SQL Server 2022 的自适应 JOIN(Adaptive Join)不会在编译阶段就锁定连接算法,而是在执行到实际数据流时,根据已读取的行数动态选择用 Hash Join 还是 Nested Loops Join。这意味着你看到的执行计划里那个带“自适应”的图标,只是个“占位符”——真正走哪条路,得看运行时数据量是否触发了阈值切换。
常见错误现象:你在 SSMS 中直接看“显示估计的执行计划”(Ctrl+L),会发现 Adaptive Join 算子下面只显示一个分支(通常是 Nested Loops),误以为它一直这么跑;但实际生产中它可能切到了 Hash Join,而你完全没察觉。
- 必须用“实际执行计划”(Ctrl+M)才能捕获真实路径,仅靠估计计划无法反映自适应行为
- 执行后,在执行计划 XML 中搜索
Adaptive="true"和ActualExecutions,确认该算子是否被激活、是否发生了切换 - 若
AdaptiveJoinType显示为HashJoin或NestedLoopsJoin,说明运行时已做出选择;若为Unknown,通常意味着未达到触发条件或被跳过 - 注意
AdaptiveThresholdRows属性值——这是 SQL Server 内部设定的切换阈值(单位:行),不是可配置项,但能帮你反推为什么这次走了 Hash 而上次没走
如何从执行计划 XML 提取自适应决策细节
SSMS 图形界面只展示简化视图,关键判断依据藏在 XML 里。右键执行计划 → “查看执行计划 XML”,然后定位到 RelOp 节点中 PhysicalOp="Adaptive Join" 的部分。
你需要重点关注这几个字段:
-
AdaptiveThresholdRows:比如值为87654,表示当驱动表输出行数 ≤ 87654 时走 Nested Loops,否则降级为 Hash Join -
AdaptiveJoinType:实际生效的类型,值为NestedLoopsJoin或HashJoin -
EstimatedJoinType:优化器最初预估的类型(仅作参考,不决定实际行为) -
ActualRows和ActualExecutions:若后者为 2,说明自适应逻辑被触发并执行了两个分支中的一个;若为 1,说明只走了默认路径
示例片段(简化):
<relop nodeid="3" physicalop="Adaptive Join" ...><adaptivejointype>HashJoin</adaptivejointype><adaptivethresholdrows>87654</adaptivethresholdrows><actualrows>92100</actualrows><actualexecutions>1</actualexecutions></relop>
为什么加了USE HINT('DISABLE_OPTIMIZER_ROWGOAL')后自适应JOIN失效
自适应 JOIN 依赖优化器对驱动表输出行数的准确估算,而 ROWGOAL 类 hint(如 TOP、OPTION(FAST N))会强制优化器低估行数,导致 AdaptiveThresholdRows 判断失准。一旦估算严重偏离,SQL Server 可能干脆跳过自适应逻辑,回退到传统 JOIN。
- 不要在含
TOP、OFFSET/FETCH或FASThint 的查询中依赖自适应 JOIN -
DISABLE_OPTIMIZER_ROWGOAL会关闭所有基于 ROWGOAL 的估算调整,但它也削弱了自适应所需的动态基础——此时优化器更倾向生成确定性计划,而非保留切换能力 - 若必须用 ROWGOAL 场景,建议显式指定
OPTION(HASH JOIN)或OPTION(LOOP JOIN),放弃自适应,换可控性
监控和复现自适应切换的关键条件
自适应不是每次都会触发。它需要满足三个硬性前提:查询启用兼容级别 150+、数据库选项 COMPATIBILITY_LEVEL ≥ 150、且执行计划中存在至少一个 JOIN 算子满足“驱动表小 + 被驱动表大 + 有合适索引”的典型模式。
- 驱动表(outer input)实际返回行数必须接近
AdaptiveThresholdRows阈值,差太远就不会切换(比如驱动表只返回 10 行,阈值是 8 万,它永远走 Nested Loops) - 被驱动表(inner input)必须能支持两种访问方式:既要有可用的索引供 Nested Loops 使用,又要允许全表/分区扫描支撑 Hash Join
- 内存压力会影响 Hash Join 分支的启用——若执行时
GrantedMemory不足,即使触发阈值,也可能降级为 Grace Hash 或直接失败 - 使用
sys.dm_exec_query_stats+query_planDMV 可批量抓取历史执行计划,筛选含Adaptive="true"的记录做趋势分析
真正难的不是看懂那个自适应图标,而是理解它背后那套“先试探、再决策、再反馈”的实时成本博弈。很多团队调优时只盯着 type 和 rows,却漏掉了 AdaptiveJoinType 和 ActualRows 的组合含义——这两者不一致,往往就是性能波动的伏笔。










