Por que uma consulta é mais lenta em um Procedimento Armazenado do que na janela Consulta?


14

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


@MartinSmith Thanks. Prefiro evitar a RECOMPILEdica, 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 usar OPTIMIZE 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.
19412 Rachel

Respostas:


5

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


0

Em uma pergunta semelhante no Stackoverflow ( com mais respostas ), verifique seu procedimento armazenado.

  • RUIM :SET ANSI_NULLS OFF (5 minutos, carretel ansioso de 6M)
  • BOM :SET ANSI_NULLS ON (0,5 segundos)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.