SQL Server--如果存储过程和计划缓存中的逻辑

Nic*_* G. 18 performance sql-server sql-server-2016 query-performance

SQL Server 2012 和 2016 标准:

如果我将if-else逻辑放在存储过程中以执行代码的两个分支之一,取决于参数的值,引擎是否缓存最新版本?

如果在接下来的执行中,参数的值发生了变化,它是否会重新编译并重新缓存存储过程,因为必须执行代码的不同分支?(此查询的编译成本非常高。)

Eri*_*ing 30

SQL Server 2012 和 2016 标准版:如果我将 if-else 逻辑放在存储过程中以执行两个代码分支之一,取决于参数的值,引擎是否缓存最新版本?

不,它缓存所有版本。或者更确切地说,它缓存一个包含所有路径的版本,并使用第一组传入的变量进行编译。所有计划的基数估计都将使用它们完成。如果某些传入的值为 NULL,这可能会特别糟糕。

这是一个使用 Stack Overflow 数据库的快速演示。

创建索引:

CREATE INDEX ix_yourmom ON dbo.Users (Reputation) INCLUDE (Id, DisplayName);
GO 
Run Code Online (Sandbox Code Playgroud)

在分支代码中创建一个带有指向不存在索引的索引提示的存储过程。

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;
    END;
    
    IF @Reputation > 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = ix_yourdad)
        WHERE u.Reputation = @Reputation;
    
    END;

END;
Run Code Online (Sandbox Code Playgroud)

如果我执行该存储过程以查找 Reputation = 1,则会出现错误。

EXEC dbo.YourMom @Reputation = 1;
Run Code Online (Sandbox Code Playgroud)

消息 308,级别 16,状态 1,过程 YourMom,第 14 行 [批处理开始行 32] 表“dbo.Users”(在 FROM 子句中指定)上的索引“ix_yourdad”不存在。

如果我们修复索引名称并重新运行查询,缓存计划如下所示:

坚果

在内部,XML 将有两个对@Reputation变量的引用。

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" />
Run Code Online (Sandbox Code Playgroud)

一个稍微简单的测试是获得存储过程的估计计划。您可以看到优化器在探索两条路径:

坚果

如果在接下来的执行中,参数的值发生变化,它是否会重新编译并重新缓存存储过程,因为必须执行代码的不同分支?(此查询的编译成本非常高。)谢谢。

不,它会保留第一次编译的运行时值。

如果我们重新执行不同的@Reputation

EXEC dbo.YourMom @Reputation = 2;
Run Code Online (Sandbox Code Playgroud)

实际计划来看:

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" ParameterRuntimeValue="(2)" />
Run Code Online (Sandbox Code Playgroud)

我们的编译值仍然为 1,但现在运行时值为 2。

在计划缓存中,您可以使用我公司开发的免费工具sp_BlitzCache 进行检查

坚果

存储过程被调用了两次,其中的每个语句都被调用了一次。

那么我们有什么?存储过程中两个查询的一个缓存计划。

如果您想要这种分支逻辑,则必须调用子存储过程:

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN
        
        EXEC dbo.Reputation1Query;

    END;
    
    IF @Reputation > 1
    BEGIN
        
        EXEC dbo.ReputationGreaterThan1Query;
    
    END;

END;
Run Code Online (Sandbox Code Playgroud)

或者动态SQL:

DECLARE @sql NVARCHAR(MAX) = N''

SET @sql +=
N'
SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u '
IF @Reputation = 1
BEGIN
    SET @sql += N' (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;'
END;


IF @Reputation > 1 
BEGIN

SET @sql += ' WITH (INDEX = ix_yourmom)
        WHERE u.Reputation = @Reputation;'

END;


EXEC sys.sp_executesql @sql;
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!