M. *_*son 7 sql-server execution-plan plan-cache
我知道与使用如下谓词编写的存储过程相关的参数嗅探问题:
CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
SELECT [Field] FROM [dbo].[Table]
WHERE [Field] = @Parameter
OR @Parameter IS NULL;
END;
Run Code Online (Sandbox Code Playgroud)
根据参数的值,第一次执行时是标量还是 NULL,一个计划被缓存,对于相反的值可能是次优的。
假设 [Field] 是标量,并且是表上的聚簇索引。以下编写存储过程以支持查询的方法的优缺点是什么:
同一个存储过程中的条件选择
CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
IF(@Parameter IS NOT NULL) BEGIN;
SELECT [Field]
FROM [dbo].[Table]
WHERE [Field] = @Parameter;
END;
ELSE BEGIN;
SELECT [Field]
FROM [dbo].[Table];
END;
END;
Run Code Online (Sandbox Code Playgroud)
存储过程中的动态 SQL
CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
DECLARE @sql NVARCHAR(MAX) = N'';
SET @sql += N'SELECT [Field]'
SET @sql += N'FROM [dbo].[Table]';
IF(@Parameter IS NOT NULL) BEGIN;
@sql += N'WHERE [Field] = @Parameter';
END;
SET @sql += N';';
EXEC sp_executesql @sql N'@Parameter INT', @Parameter;
END;
Run Code Online (Sandbox Code Playgroud)
独立的存储过程
CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
SELECT [Field]
FROM [dbo].[Table]
WHERE [Field] = @Parameter;
END;
CREATE PROCEDURE [dbo].[GetAll] AS BEGIN;
SELECT [Field]
FROM [dbo].[Table];
END;
Run Code Online (Sandbox Code Playgroud)
他们都很优秀。真的。它们都具有在缓存中有两个计划的相同影响,这正是您想要的。
随着你得到越来越多的参数,你会发现动态 SQL 选项是最清晰的,尽管它对初学者来说看起来更可怕。
如果这是一个函数,我建议避免使用多语句选项,以便 QO 可以更好地完成它的工作。
如果您使用的是最近版本的 SQL Server,则可以考虑的另一种选择是
SELECT [Field]
FROM [dbo].[Table]
WHERE [Field] = @Parameter
OR @Parameter IS NULL
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)
每次都以每次编译为代价获得运行时值的最佳计划。
您的“条件选择”选项仍然容易受到参数嗅探的影响。如果程序第一次执行时@Parameter
为 null则带有[Field] = @Parameter
谓词的分支将估计 1 行(从=NULL
谓词预期的 0 向上舍入)。
在您的问题中的特定示例中,您选择了一个列并且与您正在过滤的列相同,这不太可能出现问题,但在其他情况下可能会出现问题。
例如,在下面的例子中,第一次调用[dbo].[Get] 1
需要 333,731 次逻辑读取,因为它选择了一个不合适的带有键查找的计划。当计划从缓存中删除并重新编译时,1
逻辑读取下降到 4,330
DROP TABLE IF EXISTS [Table]
GO
CREATE TABLE [Table]
(
[Field1] INT INDEX IX,
[Field2] INT,
[Field3] INT,
);
INSERT INTO [Table]
SELECT TOP 1000000 CRYPT_GEN_RANDOM(1)%3, CRYPT_GEN_RANDOM(4), CRYPT_GEN_RANDOM(4)
FROM sys.all_objects o1, sys.all_objects o2
GO
CREATE OR ALTER PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
IF(@Parameter IS NOT NULL) BEGIN;
SELECT *
FROM [dbo].[Table]
WHERE [Field1] = @Parameter;
END;
ELSE BEGIN;
SELECT *
FROM [dbo].[Table];
END;
END;
GO
SET STATISTICS TIME ON
SET STATISTICS IO ON
EXEC [dbo].[Get]
EXEC [dbo].[Get] 1;
declare @plan_handle varbinary(64) = (select plan_handle from sys.dm_exec_procedure_stats where object_id = object_id('[dbo].[Get]'));
--Remove the plan from the cache
DBCC FREEPROCCACHE (@plan_handle);
--Re-execute it with NOT NULL passed first
EXEC [dbo].[Get] 1;
Run Code Online (Sandbox Code Playgroud)
基于 Aaron Bertrand、Martin Smith 和 Rob Farley 之前的回答和评论。我想为每种方法列出一个赞成/反对列表,包括附加方法选项(重新编译):
同一存储过程中的条件选择
来自马丁·史密斯的回应:
您的“条件选择”选项仍然容易受到参数嗅探的影响。如果在 @Parameter 为 null 时首次执行该过程,则带有 [Field] = @Parameter 谓词的分支将估计 1 行(从 =NULL 谓词预期的 0 向上舍入)。
存储过程中的动态 SQL
来自罗布·法利:
当你获得越来越多的参数时,你会发现动态 SQL 选项是最清晰的,尽管它对于初学者来说看起来更可怕。
单独的存储过程
选项(重新编译)
来自马丁·史密斯:
每次都以每次编译为代价获得运行时值的最佳计划。
我的个人收获
如果 @Parameter 的不同标量值的结果集没有显着差异,则动态 SQL 性能最佳,系统开销最小,并且与 OPTION(RECOMPILE) 相比,在管理开销方面仅稍差一些。在更复杂的场景中,参数值的变化可能会导致结果集发生重大变化,使用带有条件包含或排除 OPTION(RECOMPILE) 的动态 SQL 将是最佳的整体性能。以下是 Aaron Bertrand 详细介绍该方法的文章的链接:https ://blogs.sentryone.com/aaronbertrand/backtobasics-updated-kitchen-sink-example/
归档时间: |
|
查看次数: |
2882 次 |
最近记录: |