如何控制启动谓词在执行计划中的位置

Grz*_*Łyp 5 sql-server execution-plan startup sql-server-2016

对于下面代码的最后两个语句,生成实际的执行计划。您可以看到启动谓词 on@Par1被放置在不同的位置,这完全改变了来自test_fn1函数的实际行数。我需要控制这种行为。

create or alter function dbo.test_fn1(@Par1 varchar(100), @Par2 varchar(1))
returns @t table(item varchar(100))
as
begin
  insert into @t (item) select value from STRING_SPLIT(@Par1, @Par2);
  return
end
GO
create or alter function dbo.test_fn(@Par1 varchar(100), @Par2 varchar(100))
returns table
as
return (
  select s.*
    from dbo.test_fn1(@Par2,';') x
    inner join sys.objects s on s.name = x.item
    where @Par1 = 'CASE1'
)
GO
create or alter function dbo.test_fnx(@Par1 varchar(100), @Par2 
   varchar(100))
returns table
as
return (
  select s.*
    from sys.objects s
    inner join dbo.test_fn1(@Par2,';') x ON s.name = x.item
    where @Par1 = 'CASE1'
)
GO
declare @Par1 varchar(100), @Par2 varchar(100)
select @Par1 = 'CASE2', @Par2 = 'test1;test2'

select * from dbo.test_fn(@Par1, @Par2)
select * from dbo.test_fnx(@Par1, @Par2)
Run Code Online (Sandbox Code Playgroud)

以下是启动谓词使查询不适用于任何数据的正确计划。 在此处输入图片说明

这是显示放置启动谓词的错误行为的计划。在这两种情况下,我们都从相同的函数开始,并且仅更改了 T-SQL 中的顺序。 在此处输入图片说明

在 SQL Server 2016 SP2 上测试。

是否有任何关于 SQL Server 如何放置启动谓词的白皮书或文档?

Eri*_*ing 5

是否有任何关于 SQL Server 如何放置启动谓词的白皮书或文档?

不,不是我见过的。他们没有写那么多。有一次我写博客关于他们,我做了很多挠头。

现在,我有了一个答案——它不能保证总是有效。它在优化器上发挥了一些技巧,您可以通过观看Adam Machanic 的演讲Query Tuning Mastery: Clash of the Row了解更多信息。

有了这个,我们可以在一定程度上控制优化器决定使用 TOP 保留谓词的位置。

函数重写1:

在这个中,WHERE子句出现在CROSS APPLY.

CREATE OR ALTER FUNCTION dbo.test_erik ( @Par1 VARCHAR(100), @Par2 VARCHAR(100))
RETURNS TABLE
AS
    RETURN (   SELECT s.*
               FROM   sys.objects AS s
               CROSS APPLY
                      (   SELECT TOP (2147483647) *
                          FROM   dbo.test_fn1(@Par2, ';') AS x
                          WHERE  s.name = x.item
                          AND    @Par1 = 'CASE1' ) AS ca );
GO
Run Code Online (Sandbox Code Playgroud)

函数重写2:

在这一个中,该WHERE子句出现在CROSS APPLY.

CREATE OR ALTER FUNCTION dbo.test_erikx ( @Par1 VARCHAR(100), @Par2 VARCHAR(100))
RETURNS TABLE
AS
    RETURN (   SELECT s.*
               FROM   sys.objects AS s
               CROSS APPLY
                      (   SELECT TOP (2147483647) *
                          FROM   dbo.test_fn1(@Par2, ';') AS x
                          WHERE  s.name = x.item ) AS ca
               WHERE  @Par1 = 'CASE1' );
GO
Run Code Online (Sandbox Code Playgroud)

您可以在此处查看最终的计划

在计划 1 中,启动表达式谓词出现在 TOP 内部:

坚果

在计划 2 中,它出现在外面。

坚果

如果您希望启动表达式谓词充当常量扫描,请尝试以下操作:

CREATE OR ALTER FUNCTION dbo.test_erik_filter ( @Par1 VARCHAR(100), @Par2 VARCHAR(100))
RETURNS TABLE
AS
    RETURN (   
               SELECT ca.*
               FROM (
                     SELECT TOP (1) 1 AS n 
                     WHERE @Par1 = 'CASE1'
                    ) AS x
               CROSS APPLY 
                (
               SELECT     s.*
               FROM       sys.objects AS s
               INNER JOIN dbo.test_fn1(@Par2, ';') AS x
                   ON s.name = x.item                  
               ) AS ca

         );
GO
Run Code Online (Sandbox Code Playgroud)

如果我们运行两个不同的测试用例,计划会略有不同:

DECLARE @Par1 VARCHAR(100), @Par2 VARCHAR(100);
SELECT @Par1 = 'CASE2', @Par2 = 'test1;test2';

select * from dbo.test_erik_filter(@Par1, @Par2)

GO 

DECLARE @Par1 VARCHAR(100), @Par2 VARCHAR(100);
SELECT @Par1 = 'CASE1', @Par2 = 'test1;test2';

select * from dbo.test_erik_filter(@Par1, @Par2)

GO
Run Code Online (Sandbox Code Playgroud)

查看实时查询计划(这里是常规查询计划),有几个早期的重要差异。

没有行出来,这意味着启动表达式不正确。 坚果

行将从这个流出来,因为它确实如此。 坚果

请记住,数据在查询计划中从右向左流动。将过滤器更靠近计划的左侧对于减少工作量没有任何好处。

Aaron Bertrand 在这里讨论了一个与常规过滤器类似的问题:

顺便说一句,如果您的实际代码不只是从系统表/视图中进行选择,您应该考虑向其中添加SCHEMABINDING属性,这有助于过滤器放置。

希望这可以帮助!