为什么这些相似的查询使用不同的优化阶段(事务处理与快速计划)?

Mar*_*ith 12 sql-server optimization database-internals

此连接项中的示例代码

显示一个错误

SELECT COUNT(*)
FROM   dbo.my_splitter_1('2') L1
       INNER JOIN dbo.my_splitter_1('') L2
         ON L1.csv_item = L2.csv_item
Run Code Online (Sandbox Code Playgroud)

返回正确的结果。但以下返回不正确的结果(2014 年使用新的 Cardinality Estimator)

SELECT
    (SELECT COUNT(*)
    FROM dbo.my_splitter_1('2') L1
     INNER JOIN dbo.my_splitter_1('') L2
        ON L1.csv_item = L2.csv_item)
Run Code Online (Sandbox Code Playgroud)

因为它错误地将 L2 的结果加载到公共子表达式假脱机中,然后重播 L1 结果的结果。

我很好奇为什么两个查询之间的行为不同。Trace Flag 8675 显示工作的进入search(0) - transaction processing,失败的进入search(1) - quick plan

所以我假设额外转换规则的可用性是行为差异背后的原因(例如,禁用 BuildGbApply 或GenGbApplySimple似乎可以修复它)。

但是为什么对于这些​​非常相似的查询的两个计划会遇到不同的优化阶段?从我读过的内容来看search (0),至少需要三个表,而第一个示例中肯定不满足该条件。

Pau*_*ite 7

每个阶段都有进入条件。“至少有三个表引用”是我们在给出简单示例时谈论的入门条件之一,但它不是唯一的。

一般只允许基本的join和union进入搜索0;标量子查询、半连接等阻止进入搜索 0。这个阶段确实适用于非常常见的 OLTP 类型的查询形状。探索不太常见的事物所需的规则并未启用。您的示例查询有一个标量子查询,因此无法输入。

它还取决于您如何计算表引用。我从来没有用函数深入研究过这个问题,但逻辑可能正在计算表值函数以及它们产生的表变量。它甚至可以计算函数本身内部的表引用——我不确定;尽管我确实知道功能只是全面的艰苦工作。

这个错误GenGbApplySimple是丑陋的。这种计划形状始终是可能的,但由于成本原因而被拒绝,直到更改为 100 行假定表变量基数。USE PLAN例如,可以通过提示在 2014 年之前的 CE 上强制执行有问题的计划形状。

您认为新的 Connect 项目之前报告的问题相同,这是正确的。

举个例子,以下查询符合搜索 0 的条件:

DECLARE @T AS table (c1 integer NULL);

SELECT U.c1, rn = ROW_NUMBER() OVER (ORDER BY U.c1) 
FROM 
(
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
) AS U;
Run Code Online (Sandbox Code Playgroud)

做一个小的改变来包含一个标量子查询意味着它直接进入搜索 1:

DECLARE @T AS table (c1 integer NULL);

SELECT U.c1, rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -- Changed!
FROM 
(
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
) AS U;
Run Code Online (Sandbox Code Playgroud)