参数的执行速度不如硬编码值

时间:2012-06-20 12:20:52

标签: sql sql-server tsql sql-server-2005 stored-procedures

我有一个执行非常糟糕的存储过程。当我声明一个变量时,设置它的值,然后在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

如果我使用设定值运行存储过程,则需要一个多小时,这是执行计划。 With Variables

如果我运行存储过程删除DECLARE和SET值并将Where子句设置为以下语句,它将在不到一秒的时间内运行,这是执行计划:

WHERE(A.ColorId = 1 AND  A.SeasonId = 3)

hard coded where clause

注意硬编码变量在第一次使用哈希集时如何使用索引。这是为什么?为什么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

2 个答案:

答案 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