7

我有一个执行非常糟糕的存储过程。当我声明一个变量时,设置它的值,然后在 where 子句中使用它,该语句需要一个多小时才能运行。当我在 where 子句中对变量进行硬编码时,它会在不到一秒的时间内运行。

我开始通过执行计划来研究它的问题所在。看起来当我尝试将一些声明的变量传递给它时,执行计划会创建一些哈希匹配,因为它从使用 UNION 和公共表表达式的视图中选择值。

/************* 存储过程的开始 ******************/
创建程序 GetFruit
  @ColorId 大整数,
  @SeasionId 大整数
重新编译
作为
开始

选择
    一个名字
从
    [Apple_View] A /* 这是下面的视图 */
    INNER JOIN [水果] F
        开(F.ColorId = @ColorId
            AND A.FruitId = F.FruitId)          
在哪里
    (A.ColorId = @ColorId
    和
    A.SeasonId = @SeasonId)

结尾
/************* 存储过程结束 **************/

/************* 视图开始 ***************/
WITH Fruits (FruitId, ColorId, SeasonId) AS
(
    -- 主播成员
    选择
        F.FruitId
        ,F.ColorId
        ,F.SeasonId
    从
        ((  
            选择不同的
                EF.FruitId
                ,EF.ColorId
                ,EF.SeasonId
                ,EF.ParentFruitId
            从
                异国水果英孚
                INNER JOIN水果FR
                    ON FR.FruitId = EF.FruitId
        联盟
            选择不同的
                SF.FruitId
                ,SF.ColorId
                ,SF.SeasonId
                ,SF.ParentFruitId               
            从
                臭果SF
                INNER JOIN水果FR
                    ON FR.FruitId = SF.FruitId
        联盟
            选择不同的
                CF.FruitId
                ,CF.ColorId
                ,CF.SeasonId
                ,CF.ParentFruitId
            从
                疯狂水果CF
                INNER JOIN水果FR
                    ON FR.FruitId = CF.FruitId

            )) F

    联合所有

    -- 递归父果
    选择
        FS.FruitId
        ,FS.ColorId
        ,FS.SeasonId
        ,FS.ParentFruitId
    从
        水果FS
        INNER JOIN MasterFruit MF
            ON MF.[ParentFruitId] = fs.[FruitId]
)

选择不同的
    FS.FruitId
    ,FS.ColorId
    ,FS.SeasonId
    从
        水果FS

/************* 视图结束 ***************/


/* 执行 */
执行 GetFruit 1,3

如果我使用设置的值运行存储过程,则需要一个多小时,这是执行计划。 带变量

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

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

硬编码的where子句

注意硬编码变量如何使用索引,而第一个变量使用哈希集。这是为什么?为什么 where 子句中的硬编码值与声明的变量不同?

--------这是在@user1166147的帮助下最终完成的--------

我将存储过程更改为使用 sp_executesql。

创建程序 GetFruit
  @ColorId 大整数,
  @SeasionId 大整数
重新编译
作为
开始

声明 @SelectString nvarchar(max)

SET @SelectString = N'SELECT
    一个名字
从
    [Apple_View] A /* 这是下面的视图 */
    INNER JOIN [水果] F
        开(F.ColorId = @ColorId
            AND A.FruitId = F.FruitId)          
在哪里
    (A.ColorId = ' + CONVERT(NVARCHAR(MAX), @ColorId) + '
    和
    A.SeasonId = ' + CONVERT(NVARCHAR(MAX), @SeasonId) + ')'

执行 sp_executesql @SelectString

结尾
4

2 回答 2

2

编辑摘要 根据 Damien_The_Unbeliever 的要求

目标是在创建计划之前获取有关 SQL 变量值的最佳/最多信息,通常参数嗅探会执行此操作。在这种情况下,参数嗅探被“禁用”可能是有原因的。在没有看到实际代码的更好表示的情况下,我们无法真正说出解决方案是什么或问题存在的原因。尝试以下方法来强制受影响区域使用实际值生成计划。

*更多细节的长版*

这是您实际的存储过程吗?你的参数有默认值吗?如果是这样,它们是什么?

参数嗅探可以提供帮助 - 但它必须具有典型​​的参数值才能很好地创建计划,如果没有,将不会真正帮助或会根据非典型参数值创建一个糟糕的计划。因此,如果变量在第一次运行和编译计划时具有默认值 null 或不是典型值 - 它会创建一个错误的计划。

如果其他人编写了这个存储过程——他们可能出于某种原因故意“禁用”了对局部变量的参数嗅探。业务规则可能需要这些可变结构。

目标是在创建计划之前获取有关 SQL 变量值的最佳/最多信息,通常参数嗅探会执行此操作。但是有些事情可能会使它对性能产生负面影响,这可能就是它被“禁用”的原因。似乎仍在使用参数的非典型值或仍然没有足够的信息来创建计划 - 是否使用参数嗅探。

尝试使用使用 sp_executesql 调用存储过程中的查询来执行受影响的查询,强制它使用实际变量为该区域生成计划,并查看它是否更好。如果您必须拥有这种不规则的参数值,这可能是您的解决方案 - 创建运行受影响部分的存储过程并稍后从存储过程中调用它们 - 在变量接收到典型值之后。

如果没有看到实际代码的更好表示,就很难看出问题所在。希望这些信息会有所帮助-

于 2012-06-20T13:31:22.283 回答
0

您可以强制它根据您可能更了解的典型值来优化您的查询。在您的原始查询中添加以下内容:

OPTION (OPTIMIZE FOR(@ColorId = 1, @SeasionId = 3))

如果没有动态 SQL,它会产生类似的效果。如果你不知道典型值,你可以让优化器嗅探它们:

OPTION (OPTIMIZE FOR UNKNOWN)

同样,没有动态 SQL

于 2012-07-04T19:30:26.837 回答