Pourquoi une requête s'exécute plus lentement dans une procédure stockée que dans la fenêtre de requête?

14

J'ai une requête complexe qui s'exécute en 2 secondes dans la fenêtre de requête, mais environ 5 minutes en tant que procédure stockée. Pourquoi cela prend-il autant de temps à s'exécuter en tant que procédure stockée?

Voici à quoi ressemble ma requête.

Il prend un ensemble spécifique d'enregistrements (identifiés par @idet @createdDate), et un délai spécifique (1 an à partir de @startDate) et retourne une liste résumée des lettres envoyées et des paiements estimés reçus à la suite de ces lettres.

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

Le résultat final ressemble à ceci:

Date Lettre Code Lettres envoyées Montant payé
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

J'ai des problèmes pour déterminer où se situe le ralentissement, car tout se déroule extrêmement rapidement dans l'éditeur de requêtes. Ce n'est que lorsque je déplace la requête vers une procédure stockée qu'elle commence à s'exécuter si longtemps.

Je suis sûr que cela a quelque chose à voir avec le plan d'exécution des requêtes généré, mais je ne connais pas suffisamment SQL pour identifier la cause du problème.

Il convient probablement de noter que toutes les tables utilisées dans la requête ont des millions d'enregistrements.

Quelqu'un peut-il m'expliquer pourquoi cela prend autant de temps à s'exécuter en tant que procédure stockée que dans l'éditeur de requêtes, et m'aider à identifier la partie de ma requête qui pourrait être à l'origine de problèmes de performances lorsqu'elle est exécutée en tant que procédure stockée?

Rachel
la source
@MartinSmith Merci. Je préfère éviter l' RECOMPILEindication car je ne veux pas vraiment recompiler la requête à chaque exécution, et l'article que vous avez lié mentionnait que la copie des paramètres dans une variable locale est l'équivalent de l'utilisation OPTIMIZE FOR UNKNOWN, qui semble uniquement disponible dans 2008 et versions ultérieures. Je pense que pour l'instant, je m'en tiendrai à la copie des paramètres dans une variable locale, ce qui ramène le temps d'exécution de ma requête à 1-2 secondes.
Rachel

Réponses:

5

Comme Martin l'a souligné dans les commentaires , le problème est que la requête utilise un plan mis en cache qui est inapproprié pour les paramètres donnés.

Le lien qu'il a fourni sur Slow in the Application, Fast in SSMS? Comprendre les Mystères de la Performance a fourni beaucoup d'informations utiles qui m'ont conduit à quelques solutions.

La solution que j'utilise actuellement consiste à copier les paramètres dans des variables locales de la procédure, ce qui, je pense, fait réévaluer SQL le plan d'exécution de la requête à chaque fois qu'elle est exécutée, donc il choisit le meilleur plan d'exécution pour les paramètres donnés au lieu d'utiliser un plan mis en cache inapproprié pour la requête.

D'autres solutions qui peuvent fonctionner utilisent les conseils de requête OPTIMIZE FORou RECOMPILE.

Rachel
la source
0

À partir d'une question similaire sur Stackoverflow ( avec plus de réponses ), vérifiez votre procédure stockée.

  • MAUVAIS :SET ANSI_NULLS OFF (5 minutes, 6 m de bobine impatiente)
  • BON :SET ANSI_NULLS ON (0,5 seconde)
Ian Boyd
la source