为什么查询在存储过程中比在查询窗口中运行慢?


14

我有一个复杂的查询,它在查询窗口中运行2秒钟,但作为存储过程大约5分钟。为什么以存储过程的形式运行需要花费更长的时间?

这是我的查询的样子。

它采用一组特定的记录(由@id和标识@createdDate)和特定的时间范围(从开始的1年@startDate),并返回已发送信件的摘要列表以及由于这些信件而产生的估计付款。

CREATE PROCEDURE MyStoredProcedure
    @id int,
    @createdDate varchar(20),
    @startDate varchar(20)

 AS
SET NOCOUNT ON

    -- Get the number of records * .7
    -- Only want to return records containing letters that were sent on 70% or more of the records
    DECLARE @limit int
    SET @limit = IsNull((SELECT Count(*) FROM RecordsTable WITH (NOLOCK) WHERE ForeignKeyId = @id AND Created = @createdDate), 0) * .07

    SELECT DateSent as [Date] 
        , LetterCode as [Letter Code]
        , Count(*) as [Letters Sent]
        , SUM(CASE WHEN IsNull(P.DatePaid, '1/1/1753') BETWEEN DateSent AND DateAdd(day, 30, DateSent) THEN IsNull(P.TotalPaid, 0) ELSE 0 END) as [Amount Paid]
    INTO #tmpTable
    FROM (

        -- Letters Table. Filter for specific letters
        SELECT DateAdd(day, datediff(day, 0, LR.DateProcessed), 0) as [DateSent] -- Drop time from datetime
            , LR.LetterCode -- Letter Id
            , M.RecordId -- Record Id
        FROM LetterRequest as LR WITH (NOLOCK)
        INNER JOIN RecordsTable as M WITH (NOLOCK) ON LR.RecordId = M.RecordId
        WHERE ForeignKeyId = @id AND Received = @createdDate
            AND LR.Deleted = 0 AND IsNull(LR.ErrorDescription, '') = ''
            AND LR.DateProcessed BETWEEN @startDate AND DateAdd(year, 1, @startDate)
            AND LR.LetterCode IN ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o')
    ) as T
    LEFT OUTER JOIN (

        -- Payment Table. Payments that bounce are entered as a negative payment and are accounted for
        SELECT PH.RecordId, PH.DatePaid, PH.TotalPaid
        FROM PaymentHistory as PH WITH (NOLOCK)
            INNER JOIN RecordsTable as M WITH (NOLOCK) ON PH.RecordId = M.RecordId
            LEFT OUTER JOIN PaymentHistory as PR WITH (NOLOCK) ON PR.ReverseOfUId = PH.UID
        WHERE PH.SomeString LIKE 'P_' 
            AND PR.UID is NULL 
            AND PH.DatePaid BETWEEN @startDate AND DateAdd(day, 30, DateAdd(year, 1, @startDate))
            AND M.ForeignKeyId = @id AND M.Created = @createdDate
    ) as P ON T.RecordId = P.RecordId

    GROUP BY DateSent, LetterCode
    --HAVING Count(*) > @limit
    ORDER BY DateSent, LetterCode

    SELECT *
    FROM #tmpTable
    WHERE [Letters Sent] > @limit

    DROP TABLE #tmpTable

最终结果如下所示:

日期字母代码字母已发送金额已付
2012年1月1日1245 12345.67
2012年1月1日b 2301 1234.56
2012年1月1日c 1312 7894.45
2012年1月1日1455 2345.65
2012年1月1日c 3611 3213.21

我在找出速度下降的位置时遇到了问题,因为一切在查询编辑器中运行都非常快。只有当我将查询移动到存储过程时,它开始需要很长时间才能运行。

我确定它与生成的查询执行计划有关,但是我对SQL的了解还不足以识别可能导致问题的原因。

应该注意的是,查询中使用的所有表都有数百万条记录。

有人可以向我解释为什么作为存储过程运行需要比在查询编辑器中花费更多的时间,并且可以帮助我确定查询的哪一部分作为存储过程运行时可能导致性能问题?


@MartinSmith谢谢。我宁愿避免使用RECOMPILE提示,因为我真的不想在每次运行时都重新编译该查询,并且您链接的文章中提到将参数复制到本地变量等效于using OPTIMIZE FOR UNKNOWN,这似乎仅在2008年及以后。我想现在我将坚持将参数复制到局部变量,这使我的查询执行时间降低到1-2秒。
雷切尔

Answers:


5

正如Martin在评论中指出的那样,问题在于查询所使用的缓存计划不适用于给定的参数。

他提供的链接在应用程序中的慢速,在SSMS中快速?了解性能奥秘提供了许多有用的信息,这些信息使我找到了一些解决方案。

我当前使用的解决方案是将参数复制到过程中的局部变量,我认为这使SQL可以在运行时随时重新评估查询的执行计划,因此它为给定的参数选择最佳的执行计划,而不是使用不适用于查询的缓存计划。

其他可行的解决方案是使用OPTIMIZE FORRECOMPILE查询提示。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.