Tengo una consulta compleja que se ejecuta en 2 segundos en la ventana de consulta, pero aproximadamente 5 minutos como procedimiento almacenado. ¿Por qué tarda tanto más en ejecutarse como un procedimiento almacenado?
Así es como se ve mi consulta.
Toma un conjunto específico de registros (identificados por @id
y @createdDate
), y un marco de tiempo específico (1 año a partir de @startDate
) y devuelve una lista resumida de las cartas enviadas y los pagos estimados recibidos como resultado de esas 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
El resultado final se ve así:
Fecha Carta Código Cartas Enviadas Cantidad pagada 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
Tengo problemas para averiguar dónde está la desaceleración, porque todo se ejecuta extremadamente rápido en el editor de consultas. Es solo cuando muevo la consulta a un procedimiento almacenado que comienza a tardar tanto en ejecutarse.
Estoy seguro de que tiene algo que ver con el plan de ejecución de consultas que se genera, pero no sé lo suficiente sobre SQL para identificar qué podría estar causando el problema.
Probablemente debería notarse que todas las tablas utilizadas en la consulta tienen millones de registros.
¿Puede alguien explicarme por qué esto tarda tanto más en ejecutarse como un procedimiento almacenado que en el editor de consultas y ayudarme a identificar qué parte de mi consulta podría estar causando problemas de rendimiento cuando se ejecuta como un procedimiento almacenado?
RECOMPILE
sugerencia, ya que realmente no quiero volver a compilar la consulta cada vez que se ejecuta, y el artículo que vinculó mencionó que copiar los parámetros a una variable local es el equivalente al usoOPTIMIZE FOR UNKNOWN
, que parece estar disponible solo en 2008 y posterior. Creo que por ahora seguiré copiando los parámetros en una variable local, lo que reduce el tiempo de ejecución de mi consulta a 1-2 segundos.Respuestas:
Como Martin señaló en los comentarios , el problema es que la consulta está usando un plan en caché que no es apropiado para los parámetros dados.
¿El enlace que proporcionó en Lento en la aplicación, Rápido en SSMS? La comprensión de Performance Mysteries proporcionó mucha información útil que me llevó a algunas soluciones.
La solución que estoy usando actualmente es copiar los parámetros a las variables locales en el procedimiento, lo que creo hace que SQL reevalúe el plan de ejecución de la consulta cada vez que se ejecuta, por lo que elige el mejor plan de ejecución para los parámetros dados en lugar de usar un plan en caché inapropiado para la consulta.
Otras soluciones que pueden funcionar son las sugerencias de consulta
OPTIMIZE FOR
oRECOMPILE
.fuente
De una pregunta similar en Stackoverflow ( con más respuestas ), verifique su procedimiento almacenado.
SET ANSI_NULLS OFF
(5 minutos, carrete ansioso 6M)SET ANSI_NULLS ON
(0.5 segundos)fuente