Почему запрос выполняется в хранимой процедуре медленнее, чем в окне запроса?

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

Конечный результат выглядит так:

Дата Письмо Код Письма Отправленная сумма Оплачено
01.01.2012 a 1245 12345.67
01.01.2012 б 2301 1234,56
01.01.2012 c 1312 7894,45
01.01.2012 a 1455 2345.65
01.01.2012 c 3611 3213.21

У меня проблемы с выяснением того, где происходит замедление, потому что в редакторе запросов все работает очень быстро. Только когда я перемещаю запрос в хранимую процедуру, он начинает работать так долго.

Я уверен, что это как-то связано с созданием плана выполнения запроса, но я недостаточно знаю SQL, чтобы определить причину проблемы.

Вероятно, следует отметить, что все таблицы, используемые в запросе, содержат миллионы записей.

Может ли кто-нибудь объяснить мне, почему выполнение хранимой процедуры занимает намного больше времени, чем в редакторе запросов, и помочь мне определить, какая часть моего запроса может вызывать проблемы с производительностью при запуске в качестве хранимой процедуры?

Рейчел
источник
@MartinSmith Спасибо. Я предпочел бы избежать RECOMPILEподсказки, поскольку я не хочу перекомпилировать запрос при каждом его запуске, а в статье, на которую вы ссылались, упоминалось, что копирование параметров в локальную переменную эквивалентно использованию OPTIMIZE FOR UNKNOWN, которое, по-видимому, доступно только в 2008 и позже. Я думаю, что пока я буду придерживаться копирования параметров в локальную переменную, что сократит время выполнения моего запроса до 1-2 секунд.
Рэйчел

Ответы:

5

Как отметил Мартин в комментариях , проблема заключается в том, что в запросе используется кэшированный план, который не соответствует заданным параметрам.

Ссылка, которую он предоставил на « Медленно в приложении», «Быстро в SSMS»? Понимание загадок производительности дало много полезной информации, которая привела меня к некоторым решениям.

Решение, которое я в настоящее время использую, состоит в том, чтобы скопировать параметры в локальные переменные в процедуре, что, как мне кажется, заставляет SQL переоценивать план выполнения для запроса при каждом его выполнении, поэтому он выбирает лучший план выполнения для указанных параметров вместо использования неподходящий кэшированный план для запроса.

Другие решения, которые могут работать, используют подсказки OPTIMIZE FORили RECOMPILEзапрос.

Рейчел
источник
0

Из аналогичного вопроса о Stackoverflow ( с дополнительными ответами ) проверьте свою хранимую процедуру.

  • ПЛОХО :SET ANSI_NULLS OFF (5 минут, 6-метровая шпуля)
  • ХОРОШО :SET ANSI_NULLS ON (0,5 секунды)
Ян Бойд
источник