Perché una query viene eseguita più lentamente in una Stored Procedure rispetto alla finestra Query?


14

Ho una query complessa che viene eseguita in 2 secondi nella finestra della query, ma circa 5 minuti come Stored Procedure. Perché l'esecuzione di una procedura memorizzata richiede molto più tempo?

Ecco come appare la mia domanda.

Prende un set specifico di record (identificato da @ide @createdDate) e un intervallo di tempo specifico (1 anno a partire da @startDate) e restituisce un elenco riepilogativo di lettere inviate e pagamenti stimati ricevuti a seguito di tali lettere.

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

Il risultato finale è simile al seguente:

Data Lettera Codice Lettere Importo inviato pagato
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

Ho problemi a capire dove si trova il rallentamento, perché tutto funziona estremamente velocemente nell'editor di query. È solo quando sposto la query in una stored procedure che inizia a impiegare così tanto tempo per l'esecuzione.

Sono sicuro che abbia qualcosa a che fare con la generazione del piano di esecuzione della query, ma non so abbastanza su SQL per identificare ciò che potrebbe causare il problema.

Va probabilmente notato che tutte le tabelle utilizzate nella query hanno milioni di record.

Qualcuno può spiegarmi perché l'esecuzione di questa procedura richiede molto più tempo rispetto all'editor di query e può aiutarmi a identificare quale parte della mia query potrebbe causare problemi di prestazioni quando eseguita come procedura memorizzata?


@MartinSmith Grazie. Preferirei evitare il RECOMPILEsuggerimento poiché non voglio davvero ricompilare la query ogni volta che viene eseguita e l'articolo che hai collegato menzionava che copiare i parametri in una variabile locale è l'equivalente come usare OPTIMIZE FOR UNKNOWN, che sembra essere disponibile solo in 2008 e successive. Penso che per ora continuerò a copiare i parametri in una variabile locale, il che riduce i tempi di esecuzione della query a 1-2 secondi.
Rachel,

Risposte:


5

Come Martin ha sottolineato nei commenti , il problema è che la query utilizza un piano memorizzato nella cache che non è appropriato per i parametri forniti.

Il link che ha fornito su Lento nell'applicazione, Veloce in SSMS? La comprensione dei misteri delle prestazioni ha fornito molte informazioni utili che mi hanno portato ad alcune soluzioni.

La soluzione che sto attualmente usando è quella di copiare i parametri nelle variabili locali nella procedura, che a mio avviso rende SQL rivalutare il piano di esecuzione per la query ogni volta che viene eseguito, quindi seleziona il piano di esecuzione migliore per i parametri forniti invece di utilizzare un piano cache inappropriato per la query.

Altre soluzioni che potrebbero funzionare stanno utilizzando i suggerimenti OPTIMIZE FORo RECOMPILEquery.


0

Da una domanda simile su StackOverflow ( con più risposte ), controlla la procedura memorizzata.

  • MALE :SET ANSI_NULLS OFF (5 minuti, bobina di 6 m)
  • BUONO :SET ANSI_NULLS ON (0,5 secondi)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.