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 @id
et @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?
RECOMPILE
indication 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'utilisationOPTIMIZE 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.Réponses:
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 FOR
ouRECOMPILE
.la source
À partir d'une question similaire sur Stackoverflow ( avec plus de réponses ), vérifiez votre procédure stockée.
SET ANSI_NULLS OFF
(5 minutes, 6 m de bobine impatiente)SET ANSI_NULLS ON
(0,5 seconde)la source