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


15

SQL Server 2012和2016 Standard:

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

并且如果在随后的执行中,参数的值发生更改,由于必须执行代码的另一个分支,是否会重新编译并重新缓存存储过程?(此查询的编译成本很高。)

Answers:


27

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

不,它将缓存所有版本。或者更确切地说,它缓存具有所有路径的一个版本,并使用传入的变量进行编译。

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

创建一个索引:

CREATE INDEX ix_yourmom ON dbo.Users (Reputation) INCLUDE (Id, DisplayName);
GO 

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

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;

如果我执行该存储过程以寻找声望= 1,则会收到错误消息。

EXEC dbo.YourMom @Reputation = 1;

消息308,级别16,状态1,过程YourMom,行14 [Batch Start Line 32]表'dbo.Users'(在FROM子句中指定)的索引'ix_yourdad'不存在。

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

坚果类

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

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" />

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

坚果类

并且,如果在随后的执行中,参数的值发生更改,由于必须执行代码的另一个分支,是否会重新编译并重新缓存存储过程?(此查询的编译成本很高。)谢谢。

不,它将保留第一个编译的运行时值。

如果我们用其他命令重新执行@Reputation

EXEC dbo.YourMom @Reputation = 2;

实际计划

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" ParameterRuntimeValue="(2)" />

我们的编译值仍然为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;

还是动态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;

希望这可以帮助!

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.