Dlaczego zapytanie działa wolniej w procedurze przechowywanej niż w oknie zapytania?

14

Mam złożone zapytanie, które uruchamia się w 2 sekundy w oknie zapytania, ale około 5 minut jako procedura przechowywana. Dlaczego uruchomienie procedury przechowywanej trwa tak długo?

Oto jak wygląda moje zapytanie.

Pobiera określony zestaw rekordów (identyfikowany przez @idi @createdDate) oraz określony przedział czasowy (od 1 roku @startDate) i zwraca skróconą listę wysłanych listów i szacunkowe płatności otrzymane w wyniku tych listów.

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

Wynik końcowy wygląda następująco:

Data List Kod Litery Wysłane Kwota Płatne
01.01.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

Mam problemy z ustaleniem, gdzie jest spowolnienie, ponieważ wszystko działa wyjątkowo szybko w edytorze zapytań. Dopiero po przeniesieniu zapytania do procedury przechowywanej jego uruchomienie zajmuje tak dużo czasu.

Jestem pewien, że ma to coś wspólnego z generowaniem planu wykonania zapytania, ale nie wiem wystarczająco dużo o SQL, aby zidentyfikować przyczynę problemu.

Należy prawdopodobnie zauważyć, że wszystkie tabele użyte w zapytaniu mają miliony rekordów.

Czy ktoś może mi wyjaśnić, dlaczego uruchomienie procedury przechowywanej trwa o wiele dłużej niż w edytorze zapytań, i pomóc mi zidentyfikować, która część mojego zapytania może powodować problemy z wydajnością podczas uruchamiania jako procedura przechowywana?

Rachel
źródło
@MartinSmith Thanks. Wolałbym unikać RECOMPILEpodpowiedzi, ponieważ tak naprawdę nie chcę ponownie kompilować zapytania za każdym razem, gdy jest ono uruchamiane, a artykuł, który podlinkowałeś, mówi, że kopiowanie parametrów do zmiennej lokalnej jest równoważne użyciu OPTIMIZE FOR UNKNOWN, który wydaje się być dostępny tylko w 2008 i później. Myślę, że na razie pozostanę przy kopiowaniu parametrów do zmiennej lokalnej, co skróci czas wykonywania zapytania do 1-2 sekund.
Rachel

Odpowiedzi:

5

Jak zauważył Martin w komentarzach , problem polega na tym, że zapytanie używa planu buforowanego, który jest nieodpowiedni dla podanych parametrów.

Link, który podał na Slow w aplikacji, Fast w SSMS? Zrozumienie Performance Mysteries dostarczyło wielu przydatnych informacji, które doprowadziły mnie do niektórych rozwiązań.

Rozwiązaniem, którego obecnie używam, jest skopiowanie parametrów do zmiennych lokalnych w procedurze, co, jak sądzę, sprawia, że ​​SQL ponownie ocenia plan wykonania zapytania za każdym razem, gdy jest uruchamiany, więc wybiera najlepszy plan wykonania dla podanych parametrów zamiast używać nieodpowiedni plan buforowany dla zapytania.

Inne rozwiązania, które mogą działać, wykorzystują wskazówki OPTIMIZE FORlub RECOMPILEzapytania.

Rachel
źródło
0

Z podobnego pytania na temat Stackoverflow ( z większą liczbą odpowiedzi ) sprawdź procedurę przechowywaną.

  • ZŁE :SET ANSI_NULLS OFF (5 minut, chętna szpula 6M)
  • DOBRY :SET ANSI_NULLS ON (0,5 sekundy)
Ian Boyd
źródło