¿Por qué una consulta se ejecuta más lentamente en un procedimiento almacenado que en la ventana de consulta?

14

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 @idy @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?

Rachel
fuente
@ MartinSmith Gracias. Prefiero evitar la RECOMPILEsugerencia, 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 uso OPTIMIZE 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.
Rachel

Respuestas:

5

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 FORo RECOMPILE.

Rachel
fuente
0

De una pregunta similar en Stackoverflow ( con más respuestas ), verifique su procedimiento almacenado.

  • MALO :SET ANSI_NULLS OFF (5 minutos, carrete ansioso 6M)
  • BUENO :SET ANSI_NULLS ON (0.5 segundos)
Ian Boyd
fuente