MSSQL优化之 or 跟 union all 案例

两个MSSQL版本:
1.Microsoft SQL Server 2008 R2 (SP3-GDR) (KB4057113) - 10.50.6560.0 (X64) Dec 28 2017 15:03:48 Copyright (c) Microsoft Corporation Web Edition (64-bit) on Windows NT 6.3 <X64> (Build 9600: ) (Hypervisor)

2.Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (Intel X86) Apr 2 2010 15:53:02 Copyright (c) Microsoft Corporation Enterprise Edition on Windows NT 6.2 <X64> (Build 9200: ) (WOW64)

SQL伪代码:

select * from tabla where (@flag=0 and col=xxx) or (@flag=1 and EXISTS(select top 1 1 from table b where a.col2=b.col))

在SP3-GDR系统上用or的写法要1-3秒在RTM1秒内就出来了。
一般这种情况优化很简单,就是直接把不同的条件拆成union all 独立出来。

还有一个在不改变写法的前提下直接在后面加上  OPTION (RECOMPILE) 就可以解决
select * from tabla where (@flag=0 and col=xxx) or (@flag=1 and EXISTS(select top 1 1 from table b where a.col2=b.col)) OPTION (RECOMPILE)

根据AI的分析:

当不使用 RECOMPILE 时,SQL Server 生成的是一个通用的执行计划,它必须同时应对 @flag=0 和 @flag=1 两种情况。于是优化器只能用 Left Semi Join + Concatenation(串联两个分支)这种安全但笨重的方式,因为你看到的嵌套循环就是这种通用计划的产物。

而加上 RECOMPILE 后,每一次执行都会重新编译,优化器可以看到变量的实际值。当 @flag=0 时,编译期就会进行“常量折叠”,将整个 (@flag=1 AND EXISTS(...)) 分支直接移除,剩下就是对 sflr 直接做日期范围查询,计划变得非常简洁高效(就像你 UNION ALL 写法中的第一个分支一样)。这等于每次编译出一个“定制版”的计划,自然快。

根据情况来优化,最好还是改成union all

2. OPTION (RECOMPILE) 的弊端

它不是免费的,主要代价如下:

① CPU 与时间开销 —— 每次都硬编译

  • 执行计划不会被缓存,每次执行都要经历完整的编译过程(解析、代数化、优化)。

  • 如果你的查询是高频执行(例如每秒钟几百上千次),累计的编译 CPU 消耗会明显推高服务器负载。

② 计划缓存完全失效 —— 无法重用

  • 正常的参数化查询可以缓存在计划缓存中供后续执行重用,RECOMPILE 会跳过缓存,导致计划缓存碎片多,也影响 sys.dm_exec_query_stats 之类的 DMV 对该查询的统计跟踪(记录不到执行历史)。

③ 无法利用“参数嵌入”以外的优化

  • 虽然对当前这个 @flag 的分支问题有效,但如果查询中有其他复杂谓词或需要动态筛选的场景,它会强制每次生成新计划,可能错过一些更轻量的方案(比如 OPTIMIZE FOR 提示)。

④ 在极高并发下可能引发编译阻塞

  • 同时进来大量相同的查询请求,如果都带着 RECOMPILE,会瞬间产生大量并发编译,造成明显的 CPU 尖峰和内存分配压力(虽然 SQL Server 对编译有一定并发控制,但依然有风险)。

⑤ 无法用于某些只读或受限环境

  • 这不是问题,但需要知道的是,OPTION (RECOMPILE) 是 T-SQL 级别的提示,如果查询是构建在 ORM 或代码中拼装的,要确保提示能被正确嵌入

 如何权衡?两个终极方案

根据你的查询特性(参数通常固定为 @flag=0 或 @flag=1),下面的代价会更小,也更根本:

 
方案优势适用场景
改写为 UNION ALL 无额外编译开销,计划可重用,分治后每个分支都可以用最优索引 推荐作为最终生产写法
OPTION (RECOMPILE) 改动最小,快速修复 适合低频调用的即席查询、调试期,或无法修改 SQL 结构的场景

如果你可以在应用代码或存储过程中修改 SQL,强烈建议用 UNION ALL 替代 OR,这会让优化器从一开始就为两个分支分别生成独立高效的子计划,而不会产生任何额外编译开销。

如果实在无法修改语句结构,那么请评估执行频率:

  • 每分钟调用几次 → RECOMPILE 完全可接受;

  • 每秒钟上百次 → 考虑改写为 IF @flag=0 ... ELSE ... 两条独立语句,或使用动态 SQL 拼接,避免编译开销。

 

posted @ 2026-04-26 09:10  Tag  阅读(4)  评论(0)    收藏  举报