分配给变量

时间:2017-05-30 06:47:56

标签: sql sql-server parameter-sniffing

以下查询将在大约22秒内运行:

DECLARE @i INT, @x INT
SET    @i = 156567

SELECT 
TOP 1
    @x = AncestorId
FROM 
    dbo.tvw_AllProjectStructureParents_ChildView a
WHERE 
    ProjectStructureId = @i AND
        a.NodeTypeCode = 42 AND
        a.AncestorTypeDiffLevel = 1
OPTION (RECOMPILE)

问题在于变量赋值(实际上这一行:@x = AncestorId)。删除分配时,速度可达15毫秒! 我通过将结果插入临时表来解决它,但我认为这是一种不好的方式。

任何人都可以帮助我解决问题的根源吗?!

P.S。

糟糕的执行计划(22 s ):https://www.brentozar.com/pastetheplan/?id=Sy6a4c9bW

良好的执行计划(20 ms ):https://www.brentozar.com/pastetheplan/?id=Byg8Hc5ZZ

2 个答案:

答案 0 :(得分:4)

使用OPTION (RECOMPILE)时,SQL Server通常可以执行parameter embedding optimisation

它正在编译的计划是单次使用,因此它可以嗅探所有变量和参数的值,并将它们视为常量。

一个简单的例子显示参数嵌入优化在行动中以及分配给变量的效果如下(实际执行计划未估算)。

DECLARE @A INT = 1, 
        @B INT = 2,
        @C INT;

SELECT TOP (1) number FROM master..spt_values WHERE @A > @B;
SELECT TOP (1) number FROM master..spt_values WHERE @A > @B OPTION (RECOMPILE);
SELECT TOP (1) @C = number FROM master..spt_values WHERE @A > @B OPTION (RECOMPILE);

此计划如下:

enter image description here

请注意,中间版本甚至根本不触及表格,因为SQL Server可以在编译时推断出@A > @B不是true。但是计划3又回到计划表中,因为变量分配明显阻止了计划2中显示的OPTION (RECOMPILE)的影响。

(顺便说一下,第三个计划实际上不是第一个计划的4-5倍。分配给变量似乎也抑制了通常的行目标逻辑,其中索引扫描的成本将按比例缩小以反映TOP 1

在您的好计划中,@i的{​​{1}}值被推送到递归CTE的锚点中的搜索中,它返回0行,因此递归部分不得不起作用。

enter image description here

在糟糕的计划中,递归CTE完全实现了627,393次递归子树的执行,最后谓词应用于最终的627,393行(丢弃所有这些行)

enter image description here

我不确定为什么SQL Server无法使用变量推送谓词。您还没有提供表的定义 - 或者提供递归CTE的视图。 There is a similar issue with predicate pushing, views, and window functions though

一种解决方案是将视图更改为内联表值函数,该函数接受mainid的参数,然后将其添加到定义的锚点部分中的156567子句中。而不是依靠SQL Server为您推送谓词。

答案 1 :(得分:-2)

可能来自SELECT TOP 1。

如果只有字段,SQL Server将只占用第一行。当您具有变量赋值时,SQL Server将获取所有结果,但仅使用前一个结果。

我检查了不同的查询,但情况并非总是如此,但由于视图/表格的复杂性,SQL Server优化可能会失败。

您可以尝试以下解决方法:

DECLARE @i INT, @x INT
SET    @i = 156567

SET @x = (SELECT 
TOP 1
    AncestorId
FROM 
    dbo.tvw_AllProjectStructureParents_ChildView a
WHERE 
    ProjectStructureId = @i AND
        a.NodeTypeCode = 42 AND
        a.AncestorTypeDiffLevel = 1)