我有一个执行非常糟糕的存储过程。当我声明一个变量时,设置它的值,然后在where子句中使用它,该语句需要一个多小时才能运行。当我在where子句中对变量进行硬编码时,它会在不到一秒的时间内运行。
我开始通过执行计划调查它出了什么问题。看起来当我尝试传递一些声明的变量时,执行计划会产生一些Hash Match,因为它从使用UNION和公用表表达式的视图中选择值。
/************* Begin of Stored Procedure ***************/ CREATE PROCEDURE GetFruit @ColorId bigint, @SeasionId bigint WITH RECOMPILE AS BEGIN SELECT A.Name FROM [Apple_View] A /* This is the view down below */ INNER JOIN [Fruit] F ON ( F.ColorId = @ColorId AND A.FruitId = F.FruitId) WHERE (A.ColorId = @ColorId AND A.SeasonId = @SeasonId) END /************* End of Stored Procedure ***************/ /************* Begin of View ***************/ WITH Fruits (FruitId, ColorId, SeasonId) AS ( -- Anchor member SELECT F.FruitId ,F.ColorId ,F.SeasonId FROM (( SELECT DISTINCT EF.FruitId ,EF.ColorId ,EF.SeasonId ,EF.ParentFruitId FROM ExoticFruit EF INNER JOIN Fruit FR ON FR.FruitId = EF.FruitId UNION SELECT DISTINCT SF.FruitId ,SF.ColorId ,SF.SeasonId ,SF.ParentFruitId FROM StinkyFruit SF INNER JOIN Fruit FR ON FR.FruitId = SF.FruitId UNION SELECT DISTINCT CF.FruitId ,CF.ColorId ,CF.SeasonId ,CF.ParentFruitId FROM CrazyFruit CF INNER JOIN Fruit FR ON FR.FruitId = CF.FruitId )) f UNION ALL -- Recursive Parent Fruit SELECT FS.FruitId ,FS.ColorId ,FS.SeasonId ,FS.ParentFruitId FROM Fruits FS INNER JOIN MasterFruit MF ON MF.[ParentFruitId] = fs.[FruitId] ) SELECT DISTINCT FS.FruitId ,FS.ColorId ,FS.SeasonId FROM Fruits FS /************* End of View ***************/ /* To Execute */ EXEC GetFruit 1,3
如果我使用设定值运行存储过程,则需要一个多小时,这是执行计划。
如果我运行存储过程删除DECLARE和SET值并将Where子句设置为以下语句,它将在不到一秒的时间内运行,这是执行计划:
WHERE(A.ColorId = 1 AND A.SeasonId = 3)
注意硬编码变量在第一次使用哈希集时如何使用索引。这是为什么?为什么where子句中的硬编码值与声明的变量不同?
-------这是在@ user1166147 ------
的帮助下最终完成的我将存储过程更改为使用sp_executesql。
CREATE PROCEDURE GetFruit @ColorId bigint, @SeasionId bigint WITH RECOMPILE AS BEGIN DECLARE @SelectString nvarchar(max) SET @SelectString = N'SELECT A.Name FROM [Apple_View] A /* This is the view down below */ INNER JOIN [Fruit] F ON ( F.ColorId = @ColorId AND A.FruitId = F.FruitId) WHERE (A.ColorId = ' + CONVERT(NVARCHAR(MAX), @ColorId) + ' AND A.SeasonId = ' + CONVERT(NVARCHAR(MAX), @SeasonId) + ')' EXEC sp_executesql @SelectString END
答案 0 :(得分:2)
编辑摘要根据Damien_The_Unbeliever的请求
目标是在创建计划之前将有关变量值的最佳/大部分信息提供给SQL,通常参数嗅探就是这样做的。在这种情况下,可能有一个原因是参数嗅探被“禁用”。如果没有更好地表示实际代码,我们就无法真正说出解决方案是什么或问题存在的原因。尝试以下内容强制受影响的区域使用实际值生成计划。
* 更长细节的长版*
这是你的实际存储过程吗?你有参数的默认值吗?如果是这样,他们是什么?
参数嗅探可以提供帮助 - 但它必须具有典型的参数值才能很好地创建计划,如果没有,将无法真正帮助或将根据非典型参数值创建错误的计划。因此,如果变量的默认值为null,或者第一次运行并且计划编译时该值不是典型值,则会创建错误的计划。
如果其他人写了这个sproc - 他们可能故意'禁用'参数嗅探局部变量是有原因的。业务规则可能需要这些变量结构。
目标是在创建计划之前获取有关变量值的最佳/大部分信息,并且通常参数嗅探执行此操作。但有些事情可能会对性能产生负面影响,这可能就是“禁用”的原因。似乎仍然使用参数的非典型值创建计划,或者仍然没有足够的信息 - 使用参数嗅探与否。
尝试使用sp_executesql调用sproc内部的查询来执行受影响的查询,强制它使用实际变量生成该区域的计划,并查看它是否更好。如果您必须具有这种不规则的参数值,则可能是您的解决方案 - 在变量收到典型值之后,创建运行受影响部分的存储过程并稍后从存储过程中调用它们。
如果没有更好地表示实际代码,很难看出问题所在。希望这些信息有用 -
答案 1 :(得分:0)
您可以根据您可能更了解的典型值强制它优化您的查询。 在原始查询中添加以下内容:
OPTION (OPTIMIZE FOR(@ColorId = 1, @SeasionId = 3))
没有动态SQL会有类似的效果。 如果您不知道典型值,可以让优化器嗅探它们:
OPTION (OPTIMIZE FOR UNKNOWN)
同样,没有动态SQL