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)
希望这可以帮助!
| 归档时间: |
|
| 查看次数: |
4572 次 |
| 最近记录: |