为什么在将结果选择到标量变量中时,SQL Server 不使用 OPTION(RECOMPILE) 执行常量 (UNION ALL) 分支消除?

use*_*740 5 sql-server optimization sql-server-2017 recompile

我们使用一些“聚合”视图使用鉴别器从多个表中进行选择(注意:这些视图不是分区视图,因为鉴别器不在基表中)。这在使用 时通常效果很好option(recompile),因为查询计划器将在选择查询计划之前消除不可到达的union all路径。

然而,当将结果选择为标量变量时,这种常量折叠优化似乎失败了。将结果选择到临时表变量中不会对重新编译进行去优化。

下面是SQL Server 2017中的一个复现案例:

-- A table, don't need any data.
create table [test].test_table (col1 int, primary key (col1));

-- A simple 'aggregate' view. Using the same table here is irrelevant and,
-- while the view shows the scenario, it might not be required to reproduce the issue.
create view [test].test_view as
select col1, descrim = 1 from [test].test_table
union all
select col1, descrim = 2 from [test].test_table
Run Code Online (Sandbox Code Playgroud)

普通查询,其结果在优化的查询计划感人只有一个的的union all分支:

declare @descrim int = 2;

select count(col1)
from [test].test_view
where descrim = @descrim
option (recompile) -- explicit recompile here "works"
Run Code Online (Sandbox Code Playgroud)

但是,一旦使用了“选择到标量变量”,计划就会被取消优化,因为它不会消除未使用的联合。(在查询文本中使用字面值时,计划仍然正确优化。)

declare @descrim int = 2;
declare @brokeit int;

select @brokeit = count(col1)
from [test].test_view
where descrim = @descrim
option (recompile) -- explicit recompile here does NOT optimize plan for @descrim!
Run Code Online (Sandbox Code Playgroud)

1. 这种去优化是“预期的”吗?

2.关于和/或选择标量变量的这种重要的去优化行为在哪里option(recompile)记录或以其他方式深入讨论?

3. 有没有一种简单的方法可以在select @x = ..不使用临时表(变量)的情况下获得重新编译优化的计划?

虽然在查询执行期间union all会阻止对次要工件的实际 IO 访问,但这仍然是查询计划生成的问题。在产生此问题的特定错误情况下,保留多个表以供考虑会阻止 SQL Server 选择适当的搜索计划,并且由此产生的计划选项在给定域中是非常糟糕的选择。

第一个“好”计划:

在此处输入图片说明

第二个“坏”计划:

在此处输入图片说明

这个“坏”计划也有一个隐式转换警告,让我怀疑选择到标量变量可能绕过了许多不同的优化——甚至option(recompile)完全忽略了提示。

Pau*_*ite 7

常量折叠在 SQL Server 中具有特殊含义。它与你的问题没有直接关系。结合起来为您生成执行计划的广泛简化的功能是参数嵌入优化(PEO) 和矛盾检测

在安全的情况下,PEO 将例如参数或局部变量的文字值嵌入到查询文本中。其中一项要求OPTION (RECOMPILE)是指定的。这保证了生成的计划永远不会被重用,因此用嗅探的文字替换非文字可能是安全的。

所述OPTION (RECOMPILE)提示本身只提供了一个新的计划将在每次执行时被产生,任何参数的值嗅探将用于基数估计,并且一次性生成将不会被缓存以供执行之后的复用计划。

PEO 最初是在 SQL Server 2008 中添加到产品中的,但不久之后由于可能会出现错误结果而被禁用。它在 SQL Server 2008 SP1 CU5(Microsoft 博客文章)中重新启用。

使用 PEO 优化的查询在查询优化器看来完全就像是用文字而不是参数或变量编写的查询。矛盾检测可以删除整个子句或WHERE 0 = 1出现类似文字表达式的关系运算符子树。之所以存在此功能,是因为自动化工具通常会生成此类 SQL。

应用 PEO 并不总是安全的,但官方并未记录例外情况。一个例外是变量赋值发生的地方(其他存在,例如参数出现在OPTIMIZE FOR子句中的地方)。我的理解是变量赋值涉及大量复杂的遗留行为,偶尔会有奇怪的语义,出于向后兼容的原因而保留。保证 PEO 在所有情况下都能正确运行是不切实际的,因此在这种情况下它被禁用。

PEO 是一种机会主义设施,它超越了OPTION (RECOMPILE). 在许多情况下,它可以带来显着的性能优势,但并未正式记录在案。人们可能会将其视为一项奖励功能——当你得到它时很好,但如果失望,则不会退款。

在您的示例中,在无法应用 PEO 的情况下,启动过滤器(已记录)提供了消除子树执行的功能。“未优化”计划中显示的过滤器运算符是启动过滤器,仅在启动谓词评估为true时才执行其子树。

在 PEO 上下文中,缺少“搜索计划”通常是由于只有在存在文字值时才能执行的优化(由于安全问题或实施限制)。此文字可能出现在原始文本中,也可能已通过 PEO 替换。这方面的一个例子是优化规则SelOnSeqPrj,它允许谓词像ROW_NUMBER在安全一样移过序列函数,但仅当文字值可用时

一个SQL服务器2017年摄制的问题,不会产生额外的代码计算标量,并在问题中提到的隐式转换。用于生成该计划的查询似乎与问题中给出的查询不同。或者,实例或数据库可能有一些未指定的重要配置或选项。无论如何,我无法重现它。

OPTION (RECOMPILE)提示是永远不会忽略。查询中唯一的隐式转换是从 的内部bigint结果COUNT(*)integer所需的COUNT(与 相对COUNT_BIG)。

根据问题背后的实际应用程序的要求和限制,您可能需要使用动态 SQL 或其他一些解决方案。如果可以以适合我们的问答格式的方式表达,请随意提出有关潜在问题的潜在解决方案的新问题。


对您的问题的简要回答是:

  1. 是的。
  2. 我在Parameter Sniffing、Embedding 和 RECOMPILE Options 中介绍了它
  3. 不。