2011-01-25 24 views
11

To takie proste: zapytanie uruchamiane w ciągu kilku sekund w SQL Developer łączącym się z Oracle 11g zajmuje 15-25 minut w SSRS 2008 R2. Nie próbowałem innych wersji SSRS. Do tej pory robię wszystkie wykonanie raportu od VS 2008.Zapytanie działa szybko w Oracle SQL Developer, ale powoli w SSRS 2008 R2

Używam dostawcy OLE DB "OraOLEDB.Oracle.1", który w przeszłości wydawał się dawać mi lepsze wyniki niż korzystanie z dostawcy Oracle.

Oto co udało mi się ustalić do tej pory:

• Opóźnienie jest w fazie realizacji DataSet i nie ma nic wspólnego z wynikiem ustalonym lub czas renderingu. (Sprawdzając, wybierając ten sam zestaw wierszy bezpośrednio z tabeli, do której wstawiłem).

• Sam SSRS nie jest zawieszany. To naprawdę czeka na Oracle, gdzie jest opóźnienie (udowodnione przez zakończenie sesji DB ze strony Oracle, co spowodowało natychmiastowy błąd w SSRS o zabiciu sesji).

• Próbowałem bezpośrednich zapytań z parametrami w postaci: Parametr. Bardzo wczesne wersje mojego zapytania, które były prostsze, działały dobrze w przypadku bezpośrednich zapytań, ale wydawało się, że po pewnym stopniu złożoności zapytanie zacznie trwać wiecznie od SSRS.

• Następnie przełączyłem się do wykonywania SP, który wstawia wyniki zapytania do tabeli lub globalnej tabeli tymczasowej. Pomogło to na krótką chwilę, ale nie było to bezpośrednie zapytanie, ale wygląda na to, że zwiększona złożoność zapytań lub ich długość ostatecznie złamała tę metodę. Uwaga: uruchamianie SP działającego w tabela, ponieważ opcja "Użyj pojedynczej transakcji" zaznaczone w opcji DataSource, DataSet są następnie uruchamiane w kolejności ich pojawiania się w pliku rdl. Zestawy danych, które nie zwracają żadnych pól, są nadal uruchamiane, o ile wszystkie ich parametry są spełnione.

• Po prostu wypróbowałem funkcję zwracającą tabelę i to wciąż nie przyniosło poprawy, mimo że bezpośrednie wywołania z dosłownymi parametrami w SQL Developer powracają w ciągu 1-5 sekund.

• Baza danych, o której mowa, nie ma statystyk. Jest to część produktu stworzonego przez sprzedawcę, a nie mieliśmy czasu ani wpisania się do zarządzania, aby tworzyć/aktualizować statystyki. Grałem z podpowiedź DYNAMIC_SAMPLING, aby obliczać statystyki w locie i otrzymałem lepszy plan wykonania: bez statystyk optymalizator kosztowy źle wykorzystywał łączenie LOOP zamiast sprzężenia HASH, powodując podobne, wielokrotne czasy wykonania. W ten sposób wstawiam wskazówki do kwerendy, aby wymusić kolejność łączenia, a także spowodować, że użyje strategicznego sprzężenia hash, przywracając czas wykonania z powrotem do zaledwie kilku sekund. Nie wróciłem i spróbowałem bezpośrednich zapytań w SSRS, korzystając z tych wskazówek dotyczących wykonywania.

• Uzyskałem pomoc od Oracle DBA, która utworzyła ślad (lub jakikolwiek inny odpowiednik Oracle) i był w stanie zobaczyć uruchamiane rzeczy, ale do tej pory nie znalazł niczego przydatnego. Niestety, jego czas jest ograniczony i nie byliśmy w stanie naprawdę zagłębić się, aby dowiedzieć się, co jest wykonywane po stronie serwera. Nie mam doświadczenia, aby to zrobić szybko, ani czasu na samodzielne zbadanie, jak to zrobić. Sugestie, co zrobić, aby określić, co się dzieje, zostaną docenione.

My tylko hipotezy:

• Zapytanie jest jakoś się zły plan wykonania. Np. Niewłaściwe użycie sprzężenia LOOP zamiast sprzężenia HASH, gdy istnieją dziesiątki tysięcy wierszy "lewej" lub zewnętrznej pętli, a nie tylko kilkaset.

• SSRS może przesyłać parametry jako nvarchar (4000) lub coś zamiast czegoś sensownego, a jako parametry funkcji Oracle SP & nie mają specyfikacji długości, ale uzyskują długość ich wykonania z zapytania, to niektóre procesy ponieważ sniffowanie parametrów zakłóca plan wykonania, tak jak w poprzednim punkcie.

• Zapytanie jest w jakiś sposób przepisywane przez SSRS/dostawcę. Używam parametru wielowartościowego, ale nie takiego jak jest: parametr jest przesyłany jako wyrażenie Join (Parametry! MultiValuedParameter.Value, ","), więc nie powinno być potrzeby przepisywania. Wystarczy proste wiązanie i przesyłanie. Nie rozumiem, jak to może być w SP, a funkcja dzwoni, ale co to może być?

Rozumiem, że jest to bardzo skomplikowane i długie zapytanie, ale robi dokładnie to, czego potrzebuję. Działa w ciągu 1-5 sekund w zależności od ilości żądanych danych. Niektóre z przyczyn złożoności są:

  • Prawidłowo obsługi kosztów lista Centrum parametrów oddzielonych przecinkami
  • Umożliwienie tygodniowy rozkład być opcjonalne i jeśli włączone, zapewniając wszystkie tygodnie w miesiącu przedstawiono nawet jeśli nie ma dla nich danych.
  • Pokazywanie "Brak faktur", gdy jest to właściwe.
  • Dopuszczenie zmiennej liczby miesięcy podsumowujących.
  • Posiadanie opcjonalnej sumy YTD.
  • Łącznie z poprzednimi/historycznymi danymi porównawczymi oznacza, że ​​nie mogę po prostu użyć tych dostawców z miesiąca, muszę pokazać wszystkich dostawców, którzy znajdą się w dowolnej kolumnie historycznej.

W każdym razie, oto zapytanie, wersja SP (choć nie sądzę, że będzie to pomocne).

create or replace 
PROCEDURE VendorInvoiceSummary (
    FromDate IN date, 
    ToDate IN date, 
    CostCenterList IN varchar2, 
    IncludeWeekly IN varchar2, 
    ComparisonMonths IN number, 
    IncludeYTD IN varchar2 
) 
AS 
BEGIN 

INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt) 
SELECT 
    Mo, 
    CostCenter, 
    Vendor, 
    VendorName, 
    Section, 
    TimeUnit, 
    Amt 
FROM (
    WITH CostCenters AS (
     SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || '    ', 1, 15) CostCenter 
     FROM DUAL 
     CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1 
    ), Invoices AS (
     SELECT /*+ORDERED USE_HASH(D)*/ 
     TRUNC(I.Invoice_Dte, 'YYYY') Yr, 
     TRUNC(I.Invoice_Dte, 'MM') Mo, 
     D.Dis_Acct_Unit CostCenter, 
     I.Vendor, 
     V.Vendor_VName, 
     CASE 
      WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate 
      THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1 
      ELSE 0 
     END WkNum, 
     Sum(D.To_Base_Amt) To_Base_Amt 
     FROM 
     ICCompany C 
     INNER JOIN APInvoice I 
      ON C.Company = I.Company 
     INNER JOIN APDistrib D 
      ON C.Company = D.Company 
      AND I.Invoice = D.Invoice 
      AND I.Vendor = D.Vendor 
      AND I.Suffix = D.Suffix 
     INNER JOIN CostCenters CC 
      ON D.Dis_Acct_Unit = CC.CostCenter 
     INNER JOIN APVenMast V ON I.Vendor = V.Vendor 
     WHERE 
     D.Cancel_Seq = 0 
     AND I.Cancel_Seq = 0 
     AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')) 
     AND I.Invoice_Dte < ToDate 
     AND V.Vendor_Group = '1 ' -- index help 
     GROUP BY 
     TRUNC(I.Invoice_Dte, 'YYYY'), 
     TRUNC(I.Invoice_Dte, 'MM'), 
     D.Dis_Acct_Unit, 
     I.Vendor, 
     V.Vendor_VName, 
     CASE 
      WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate 
      THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1 
      ELSE 0 
     END 
    ), Months AS (
     SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo 
     FROM DUAL 
     CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))) 
    ), Sections AS (
     SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL 
     UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL 
     UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y' 
    ), Vals AS (
     SELECT LEVEL - 1 TimeUnit 
     FROM DUAL 
     CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1 
    ), TimeUnits AS (
     SELECT S.Section, V.TimeUnit 
     FROM 
     Sections S 
     INNER JOIN Vals V 
      ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit 
    ), Names AS (
     SELECT DISTINCT 
     M.Mo, 
     Coalesce(I.Vendor, '0') Vendor, 
     Coalesce(I.Vendor_VName, 'No Paid Invoices') Vendor_VName, 
     Coalesce(I.CostCenter, ' ') CostCenter 
     FROM 
     Months M 
     LEFT JOIN Invoices I 
      ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) < I.Mo 
      AND M.Mo >= I.Mo 
     WHERE 
     M.Mo >= FromDate 
     AND M.Mo < ToDate 
    ) 
    SELECT 
     N.Mo, 
     N.CostCenter, 
     N.Vendor, 
     N.Vendor_VName VendorName, 
     T.Section, 
     T.TimeUnit, 
     Sum(I.To_Base_Amt) Amt 
    FROM 
     Names N 
     CROSS JOIN TimeUnits T 
     LEFT JOIN Invoices I 
     ON N.CostCenter = I.CostCenter 
     AND N.Vendor = I.Vendor 
     AND (
      (
       T.Section = 1 -- Weeks for current month 
       AND N.Mo = I.Mo 
       AND T.TimeUnit = I.WkNum 
      ) OR (
       T.Section = 2 -- Summary months 
       AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo 
      ) OR (
       T.Section = 3 -- YTD 
       AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo 
      ) 
     ) 
    WHERE 
     N.Mo >= FromDate 
     AND N.Mo < ToDate 
     AND NOT (-- Only 4 weeks when a month is less than 28 days long 
     T.Section = 2 
     AND T.TimeUnit = 5 
     AND TRUNC(N.Mo + 28, 'MM') <> N.Mo 
     AND I.CostCenter IS NULL 
    ) AND (
     T.Section <> 1 
     OR IncludeWeekly = 'Y' 
    ) 
    GROUP BY 
     N.Mo, 
     N.CostCenter, 
     N.Vendor, 
     N.Vendor_VName, 
     T.Section, 
     T.TimeUnit 
) X; 
COMMIT; 
END; 

UPDATE

Nawet po nauki o planach realizacji Oracle i podpowiedzi (tłumaczenie moje SQL wiedzy Server), wciąż nie mógł dostać zapytanie do szybkiego uruchomienia w SSRS aż zrobiłem go uruchomić w dwóch krokach, najpierw umieścić wyniki prawdziwego stołu w postaci GLOBAL TEMPORARY TABLE, a następnie drugie, aby wyodrębnić dane z tego. DYNAMIC_SAMPLING dał mi dobry plan wykonania, który następnie skopiowałem za pomocą wskazówek dołączenia i dostępu. Oto ostateczny SP (nie może to być funkcja, ponieważ w Oracle nie można wykonać DML w funkcji, gdy ta funkcja jest wywoływana wewnątrz instrukcji SELECT):

Czasami przysięgam, że ignorowałem moje wskazówki do dołączenia, takie jak jako swap_join_inputs i no_swap_join_inputs, ale z mojego czytania najwyraźniej Oracle tylko ignoruje podpowiedzi, gdy nie można ich faktycznie użyć lub robisz coś złego. Na szczęście tabele zostały odpowiednio zamienione (tak jak w przypadku USE_NL(CC) niezawodnie umieszcza tabelę CC jako zamienione, lewe wejście, nawet jeśli jest dołączone jako ostatnie).

CREATE OR REPLACE 
PROCEDURE VendorInvoicesSummary (
    FromDate IN date, 
    ToDate IN date, 
    CostCenterList IN varchar2, 
    IncludeWeekly IN varchar2, 
    ComparisonMonths IN number, 
    IncludeYTD IN varchar2 
) 
AS 
BEGIN 

INSERT INTO InvoiceTemp (Yr, Mo, CostCenter, Vendor, WkNum, Amt) -- A global temporary table 
SELECT /*+LEADING(C I D CC) USE_HASH(I D) USE_NL(CC)*/ 
    TRUNC(I.Invoice_Dte, 'YYYY') Yr, 
    TRUNC(I.Invoice_Dte, 'MM') Mo, 
    D.Dis_Acct_Unit CostCenter, 
    I.Vendor, 
    CASE 
     WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate 
     THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1 
     ELSE 0 
    END WkNum, 
    Sum(D.To_Base_Amt) To_Base_Amt 
FROM 
    ICCompany C 
    INNER JOIN APInvoice I 
     ON C.Company = I.Company 
    INNER JOIN APDistrib D 
     ON C.Company = D.Company 
     AND I.Invoice = D.Invoice 
     AND I.Vendor = D.Vendor 
     AND I.Suffix = D.Suffix 
    INNER JOIN (
     SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || '    ', 1, 15) CostCenter 
     FROM DUAL 
     CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1 
    ) CC ON D.Dis_Acct_Unit = CC.CostCenter 
WHERE 
    D.Cancel_Seq = 0 
    AND I.Cancel_Seq = 0 
    AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')) 
    AND I.Invoice_Dte < ToDate 
GROUP BY 
    TRUNC(I.Invoice_Dte, 'YYYY'), 
    TRUNC(I.Invoice_Dte, 'MM'), 
    D.Dis_Acct_Unit, 
    I.Vendor, 
    CASE 
     WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate 
     THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1 
     ELSE 0 
    END; 

INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt) 
SELECT 
    Mo, 
    CostCenter, 
    Vendor, 
    VendorName, 
    Section, 
    TimeUnit, 
    Amt 
FROM (
    WITH Months AS (
     SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo 
     FROM DUAL 
     CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))) 
    ), Sections AS (
     SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL 
     UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL 
     UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y' 
    ), Vals AS (
     SELECT LEVEL - 1 TimeUnit 
     FROM DUAL 
     CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1 
    ), TimeUnits AS (
     SELECT S.Section, V.TimeUnit 
     FROM 
     Sections S 
     INNER JOIN Vals V 
      ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit 
    ), Names AS (
     SELECT DISTINCT 
     M.Mo, 
     Coalesce(I.Vendor, '0') Vendor, 
     Coalesce(I.CostCenter, ' ') CostCenter 
     FROM 
     Months M 
     LEFT JOIN InvoiceTemp I 
      ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) <= I.Mo 
      AND I.Mo <= M.Mo 
     WHERE 
     M.Mo >= FromDate 
     AND M.Mo < ToDate 
    ) 
    SELECT 
     N.Mo, 
     N.CostCenter, 
     N.Vendor, 
     Coalesce(V.Vendor_VName, 'No Paid Invoices') VendorName, 
     T.Section, 
     T.TimeUnit, 
     Sum(I.Amt) Amt 
    FROM 
     Names N 
     INNER JOIN APVenMast V ON N.Vendor = V.Vendor 
     CROSS JOIN TimeUnits T 
     LEFT JOIN InvoiceTemp I 
     ON N.CostCenter = I.CostCenter 
     AND N.Vendor = I.Vendor 
     AND (
      (
       T.Section = 1 -- Weeks for current month 
       AND N.Mo = I.Mo 
       AND T.TimeUnit = I.WkNum 
      ) OR (
       T.Section = 2 -- Summary months 
       AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo 
      ) OR (
       T.Section = 3 -- YTD 
       AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo 
      ) 
     ) 
    WHERE 
     N.Mo >= FromDate 
     AND N.Mo < ToDate 
     AND V.Vendor_Group = '1 ' 
     AND NOT (-- Only 4 weeks when a month is less than 28 days long 
     T.Section = 2 
     AND T.TimeUnit = 5 
     AND TRUNC(N.Mo + 28, 'MM') <> N.Mo 
     AND I.CostCenter IS NULL 
    ) AND (
     T.Section <> 1 
     OR IncludeWeekly = 'Y' 
    ) 
    GROUP BY 
     N.Mo, 
     N.CostCenter, 
     N.Vendor, 
     V.Vendor_VName, 
     T.Section, 
     T.TimeUnit 
) X; 

COMMIT; 
END; 

To była długa, bolesna jazdy, ale jeśli jest jedna rzecz ja nauczyłem jest to, że działa w bazie danych bez odpowiednio aktualizowanych statystyk (które mam zamiar spojrzeć na uzyskanie naszego DBA, aby dodać jeszcze chociaż sprzedawca nie dba o nie) może być prawdziwą katastrofą dla kogoś, kto chce załatwić sprawy w rozsądnym czasie.

+0

czy Twoje statystyki są aktualne? –

+0

Nie, jak już powiedziałem, sprzedawca nie używa statystyk. Opierają się one na regułach dla każdego zapytania w tym DB, które ma starożytne pochodzenie. – ErikE

Odpowiedz

1

Publikowanie zapytania może pomóc.

Twój DBA powinien być w stanie zidentyfikować sesję w widoku zwanym v $ sesją, a kolumny EVENT i WAIT_CLASS powinny wskazywać, co dzieje się na końcu Oracle.

Byłby również w stanie zidentyfikować SQL (SQL_ID z sesji v $) i użyć go w SELECT * FROM TABLE (DBMS_XPLAN.DISPLAY_CURSOR (sql_id)), aby określić plan.

Jeśli jest to instancja deweloperska/testowa, sprawdź, czy udzieli Ci uprawnień do wykonania tej czynności samodzielnie, jeśli jest zajęta.

+0

Dodano zapytanie. Przyjrzę się twoim innym sugestiom. – ErikE

+0

Dziękuję. Zobaczenie planu wykonania (CAŁKOWICIE odmiennego od tego, który otrzymałem od programisty SQL) udowodniło, że rzeczywiście był to problem. Teraz jestem w lesie, zastanawiając się, jak wymusić plan wykonania, którego potrzebuję. Westchnienie. – ErikE

+0

P.S. Właściwie to zmagałem się i walczyłem i nigdy nie byłem w stanie sprawić, by działał niezawodnie, dopóki nie wprowadzę wyników głównego dostępu do tabeli GLOBAL TEMPORARY, a następnie dołączyłem do tego w dalszej części zapytania. '/ * + MATERIALIZE * /' również nie pomagało. Niektóre z nich wciąż są dla mnie tajemnicą. Bolesna, bolesna tajemnica. Godziny, które zmarnowałem na to ... – ErikE

1

Wiem, że jest stary, ale mieliśmy podobny problem i musieliśmy ustawić nsl_sort na binarny zamiast binary_ci. Ludzie mogą spróbować ustawić sesję na binarną: zmień zestaw sesji nls_sort = binarny

Powiązane problemy