У меня сложный запрос, который выполняется в течение 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, чтобы определить причину проблемы.
Вероятно, следует отметить, что все таблицы, используемые в запросе, содержат миллионы записей.
Может ли кто-нибудь объяснить мне, почему выполнение хранимой процедуры занимает намного больше времени, чем в редакторе запросов, и помочь мне определить, какая часть моего запроса может вызывать проблемы с производительностью при запуске в качестве хранимой процедуры?
RECOMPILE
подсказки, поскольку я не хочу перекомпилировать запрос при каждом его запуске, а в статье, на которую вы ссылались, упоминалось, что копирование параметров в локальную переменную эквивалентно использованиюOPTIMIZE FOR UNKNOWN
, которое, по-видимому, доступно только в 2008 и позже. Я думаю, что пока я буду придерживаться копирования параметров в локальную переменную, что сократит время выполнения моего запроса до 1-2 секунд.Ответы:
Как отметил Мартин в комментариях , проблема заключается в том, что в запросе используется кэшированный план, который не соответствует заданным параметрам.
Ссылка, которую он предоставил на « Медленно в приложении», «Быстро в SSMS»? Понимание загадок производительности дало много полезной информации, которая привела меня к некоторым решениям.
Решение, которое я в настоящее время использую, состоит в том, чтобы скопировать параметры в локальные переменные в процедуре, что, как мне кажется, заставляет SQL переоценивать план выполнения для запроса при каждом его выполнении, поэтому он выбирает лучший план выполнения для указанных параметров вместо использования неподходящий кэшированный план для запроса.
Другие решения, которые могут работать, используют подсказки
OPTIMIZE FOR
илиRECOMPILE
запрос.источник
Из аналогичного вопроса о Stackoverflow ( с дополнительными ответами ) проверьте свою хранимую процедуру.
SET ANSI_NULLS OFF
(5 минут, 6-метровая шпуля)SET ANSI_NULLS ON
(0,5 секунды)источник