Eu tenho uma consulta complexa que é executada em 2 segundos na janela de consulta, mas cerca de 5 minutos como um procedimento armazenado. Por que está demorando muito mais para ser executado como um procedimento armazenado?
Aqui está a aparência da minha consulta.
Ele pega um conjunto específico de registros (identificado por @id
e @createdDate
) e um período de tempo específico (1 ano a partir de @startDate
) e retorna uma lista resumida de cartas enviadas e pagamentos estimados recebidos como resultado dessas cartas.
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
O resultado final é assim:
Data Carta Código Cartas Enviadas Valor Pago 1/1/2012 a 1245 12345,67 1/1/2012 b 2301 1234,56 1/1/2012 c 1312 7894,45 1/1/2012 a 1455 2345,65 1/1/2012 c 3611 3213,21
Estou tendo problemas para descobrir onde está a desaceleração, porque tudo corre extremamente rápido no editor de consultas. Somente quando movo a consulta para um procedimento armazenado é que ela demora tanto para ser executada.
Tenho certeza de que tem algo a ver com o plano de execução da consulta sendo gerado, mas não sei o suficiente sobre SQL para identificar o que pode estar causando o problema.
Provavelmente, deve-se notar que todas as tabelas usadas na consulta possuem milhões de registros.
Alguém pode me explicar por que isso está demorando muito mais para ser executado como um procedimento armazenado do que no editor de consultas e me ajudar a identificar qual parte da minha consulta pode estar causando problemas de desempenho quando executada como um procedimento armazenado?
RECOMPILE
dica, já que não quero recompilar a consulta toda vez que ela é executada, e o artigo que você vinculou mencionou que copiar parâmetros para uma variável local é equivalente a usarOPTIMIZE FOR UNKNOWN
, o que parece estar disponível apenas em 2008 e mais tarde. Eu acho que, por enquanto, continuarei copiando parâmetros para uma variável local, o que reduz o tempo de execução da minha consulta para 1-2 segundos.Respostas:
Como Martin apontou nos comentários , o problema é que a consulta está usando um plano em cache inadequado para os parâmetros fornecidos.
O link que ele forneceu no Slow in the Application, Fast in SSMS? Noções básicas sobre desempenho Os mistérios forneceram muitas informações úteis que me levaram a algumas soluções.
A solução que estou usando atualmente é copiar os parâmetros para variáveis locais no procedimento, que acho que faz o SQL reavaliar o plano de execução da consulta a qualquer momento em que é executado, para escolher o melhor plano de execução para os parâmetros fornecidos, em vez de usar um plano em cache inadequado para a consulta.
Outras soluções que podem funcionar estão usando as dicas de consulta
OPTIMIZE FOR
ouRECOMPILE
.fonte
Em uma pergunta semelhante no Stackoverflow ( com mais respostas ), verifique seu procedimento armazenado.
SET ANSI_NULLS OFF
(5 minutos, carretel ansioso de 6M)SET ANSI_NULLS ON
(0,5 segundos)fonte