恒定扫描假脱机


14

我有一张几十行的桌子。简化的设置如下

CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

我有一个查询,可以将该表连接到一组表值构造的行(由变量和常量组成)上,例如

DECLARE @id1 int = 101, @id2 int = 105;

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (@id1, 'A'),
        (@id2, 'B')
    ) p([Id], [Code])
    FULL JOIN #data d ON d.[Id] = p.[Id];

查询执行计划表明,优化器的决策是使用FULL LOOP JOIN策略,这似乎是适当的,因为两个输入都只有很少的行。我注意到(但不同意)的一件事是,TVC行被假脱机了(请参阅红色框中的执行计划区域)。

恒定扫描假脱机

为什么优化器在这里引入假脱机,原因是什么?线轴之外没有什么复杂的。看起来好像没有必要。在这种情况下如何摆脱它,可能的方法是什么?


以上计划是在

Microsoft SQL Server 2014(SP2-CU11)(KB4077063)-12.0.5579.0(X64)


在feedback.azure.com上的相关建议
i-one,

Answers:


19

为什么优化器在这里引入假脱机,原因是什么?线轴之外没有什么复杂的。

假脱机之外的东西不是简单的表引用,当生成左联接/反半联接替代方案时,可以简单地复制该表引用。

它可能看起来像一个表(恒定扫描),但对于优化器*,它是子句中UNION ALL单独行的一个VALUES

额外的复杂度足以使优化器选择假脱机并重播源行,而以后不再用简单的“表获取”来替换假脱机。例如,完全连接的初始转换如下所示:

早期计划

注意常规转换引入的额外线轴。简单表get上方的线轴稍后将被规则清理SpoolGetToGet

如果优化器具有相应的SpoolConstGetToConstGet规则,则原则上它可以按您希望的方式工作。

在这种情况下如何摆脱它,可能的方法是什么?

使用实际表(临时表或变量表),或手动编写完全连接的转换,例如:

WITH 
    p([Id], [Code]) AS
    (
        SELECT @id1, 'A'
        UNION ALL
        SELECT @id2, 'B'
    ),
    FullJoin AS
    (
        SELECT
            p.Code,
            d.[Status]
        FROM p
        LEFT JOIN #data d 
            ON d.[Id] = p.[Id]
        UNION ALL
        SELECT
            NULL,
            D.[Status]
        FROM #data AS D
        WHERE NOT EXISTS
        (
            SELECT *
            FROM p
            WHERE p.Id = D.Id
        )
    )
SELECT
    COALESCE(FullJoin.Code, 'X') AS Code,
    COALESCE(FullJoin.Status, 0) AS [Status]
FROM FullJoin;

计划手动重写:

手动改写计划

估计成本为0.0067201单位,而原始成本为0.0203412单位。


*可以LogOp_UnionAll转换树(TF 8605)中观察为。在输入树(TF 8606)中,它是一个LogOp_ConstTableGet。该转换后的树示出了后解析,归一化,algebrization,装订,以及其他一些准备工作优化表达元件的树。的输入树示出了转换到否定范式(NNF转换),运行时间常数塌陷,和其他一些位和鲍勃后的元素。NNF转换包括折叠逻辑联合和公用表获取的逻辑等。


3

表假脱机只是根据VALUES子句中存在的两组元组创建一个表。

您可以通过首先将这些值插入到临时表中来消除假脱机,如下所示:

DROP TABLE IF EXISTS #data;
CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

DROP TABLE IF EXISTS #p;
CREATE TABLE #p
(
    Id int NOT NULL
    , Code char(1) NOT NULL
);

DECLARE @id1 int = 101, @id2 int = 105;

INSERT INTO #p (Id, Code)
VALUES
        (@id1, 'A'),
        (@id2, 'B');


SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM #p p
    FULL JOIN #data d ON d.[Id] = p.[Id];

查看查询的执行计划,我们看到输出列表包含使用Union前缀的两列。这表明假脱机正在从联合的源中创建表:

在此处输入图片说明

FULL OUTER JOIN需要访问SQL Server中的值p两次,一次为每个加盟“方”。创建后台处理程序允许生成的内部循环连接访问后台处理的数据。

有趣的是,如果将FULL OUTER JOINa 替换为LEFT JOINa RIGHT JOIN,然后UNION将结果替换在一起,则SQL Server不会使用假脱机。

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    LEFT JOIN #data d ON d.[Id] = p.[Id]
UNION
SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    RIGHT JOIN #data d ON d.[Id] = p.[Id];

在此处输入图片说明

注意,我不建议使用UNION上面的查询。对于更大的输入集,它可能不会比FULL OUTER JOIN您已经拥有的简单方法更有效。


在您的实际工作量中,线轴真的那么贵吗?
Max Vernon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.