Answers:
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;
希望这可以帮助!