Ich habe eine komplexe Abfrage, die in 2 Sekunden im Abfragefenster ausgeführt wird, aber ungefähr 5 Minuten als gespeicherte Prozedur. Warum dauert die Ausführung als gespeicherte Prozedur so lange?
So sieht meine Abfrage aus.
Es werden bestimmte Datensätze benötigt (gekennzeichnet durch @id
und@createdDate
) sowie ein bestimmter Zeitraum (ab 1 Jahr @startDate
) benötigt, und es wird eine zusammengefasste Liste der gesendeten Briefe und der geschätzten Zahlungen zurückgegeben, die als Ergebnis dieser Briefe eingegangen sind.
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
Das Endergebnis sieht so aus:
Datum Brief Code Briefe gesendet Betrag bezahlt 01.01.2012 a 1245 12345.67 01.01.2012 b 2301 1234.56 01.01.2012 c 1312 7894.45 01.01.2012 a 1455 2345.65 01.01.2012 c 3611 3213.21
Ich habe Probleme herauszufinden, wo die Verlangsamung ist, weil im Abfrage-Editor alles extrem schnell läuft. Erst wenn ich die Abfrage in eine gespeicherte Prozedur verschiebe, dauert die Ausführung so lange.
Ich bin sicher, dass dies etwas mit der Erstellung des Abfrageausführungsplans zu tun hat, aber ich weiß nicht genug über SQL, um zu identifizieren, was das Problem verursachen könnte.
Es ist wahrscheinlich zu beachten, dass alle in der Abfrage verwendeten Tabellen Millionen von Datensätzen enthalten.
Kann mir jemand erklären, warum die Ausführung als gespeicherte Prozedur so viel länger dauert als im Abfrage-Editor, und mir dabei helfen, herauszufinden, welcher Teil meiner Abfrage bei der Ausführung als gespeicherte Prozedur Leistungsprobleme verursachen kann?
RECOMPILE
Hinweis zu vermeiden, da ich die Abfrage nicht jedes Mal neu kompilieren möchte, wenn sie ausgeführt wird, und der Artikel, den Sie verlinkt haben, erwähnte, dass das Kopieren von Parametern in eine lokale Variable der Verwendung entsprichtOPTIMIZE FOR UNKNOWN
, die anscheinend nur in verfügbar ist 2008 und später. Ich denke, im Moment bleibe ich beim Kopieren von Parametern in eine lokale Variable, was meine Ausführungszeit für Abfragen wieder auf 1-2 Sekunden reduziert.Antworten:
Wie Martin in den Kommentaren betonte , besteht das Problem darin, dass die Abfrage einen zwischengespeicherten Plan verwendet, der für die angegebenen Parameter ungeeignet ist.
Der Link, den er auf Langsam in der Anwendung, Schnell in SSMS bereitgestellt hat? Das Verständnis von Performance Mysteries lieferte viele nützliche Informationen, die mich zu einigen Lösungen führten.
Die Lösung, die ich derzeit verwende, besteht darin, die Parameter in die lokalen Variablen in der Prozedur zu kopieren. Ich denke, dass SQL den Ausführungsplan für die Abfrage jedes Mal neu auswertet, wenn sie ausgeführt wird, sodass der beste Ausführungsplan für die angegebenen Parameter ausgewählt wird, anstatt zu verwenden ein ungeeigneter zwischengespeicherter Plan für die Abfrage.
Andere Lösungen, die möglicherweise funktionieren, verwenden die
OPTIMIZE FOR
oderRECOMPILE
Abfragehinweise.quelle
Überprüfen Sie anhand einer ähnlichen Frage zu Stackoverflow ( mit mehr Antworten ) Ihre gespeicherte Prozedur.
SET ANSI_NULLS OFF
(5 Minuten, 6M eifrige Spule)SET ANSI_NULLS ON
(0,5 Sekunden)quelle