2015-04-17 19 views
8

Używam PostgreSQL 9.2.9 i mam następujący problem.Funkcja PostgreSQL wykonywana znacznie dłużej niż to samo zapytanie

Istnieje funkcja:

CREATE OR REPLACE FUNCTION report_children_without_place(text, date, date, integer) 
RETURNS TABLE (department_name character varying, kindergarten_name character varying, a1 bigint) AS $BODY$ 
BEGIN 
    RETURN QUERY WITH rh AS (
     SELECT (array_agg(status ORDER BY date DESC))[1] AS status, request 
     FROM requeststatushistory 
     WHERE date <= $3 
     GROUP BY request 
    ) 
    SELECT 
     w.name, 
     kgn.name, 
     COUNT(*) 
    FROM kindergarten_request_table_materialized kr 
    JOIN rh ON rh.request = kr.id 
    JOIN requeststatuses s ON s.id = rh.status AND s.sysname IN ('confirmed', 'need_meet_completion', 'kindergarten_need_meet') 
    JOIN workareas kgn ON kr.kindergarten = kgn.id AND kgn.tree <@ CAST($1 AS LTREE) AND kgn.active 
    JOIN organizationforms of ON of.id = kgn.organizationform AND of.sysname IN ('state','municipal','departmental') 
    JOIN workareas w ON w.tree @> kgn.tree AND w.active 
    JOIN workareatypes mt ON mt.id = w.type AND mt.sysname = 'management' 
    WHERE kr.requestyear = $4 
    GROUP BY kgn.name, w.name 
    ORDER BY w.name, kgn.name; 
END 
$BODY$ LANGUAGE PLPGSQL STABLE; 

EXPLAIN ANALYZE SELECT * FROM report_children_without_place('83.86443.86445', '14-04-2015', '14-04-2015', 2014); 

Razem środowiska wykonawczego: 242805.085 ms. Ale zapytania z ciała funkcja jest wykonywana znacznie szybciej:

EXPLAIN ANALYZE WITH rh AS (
SELECT (array_agg(status ORDER BY date DESC))[1] AS status, request 
FROM requeststatushistory 
WHERE date <= '14-04-2015' 
GROUP BY request 
) 
SELECT 
    w.name, 
    kgn.name, 
    COUNT(*) 
FROM kindergarten_request_table_materialized kr 
JOIN rh ON rh.request = kr.id 
JOIN requeststatuses s ON s.id = rh.status AND s.sysname IN ('confirmed', 'need_meet_completion', 'kindergarten_need_meet') 
JOIN workareas kgn ON kr.kindergarten = kgn.id AND kgn.tree <@ CAST('83.86443.86445' AS LTREE) AND kgn.active 
JOIN organizationforms of ON of.id = kgn.organizationform AND of.sysname IN ('state','municipal','departmental') 
JOIN workareas w ON w.tree @> kgn.tree AND w.active 
JOIN workareatypes mt ON mt.id = w.type AND mt.sysname = 'management' 
WHERE kr.requestyear = 2014 
GROUP BY kgn.name, w.name 
ORDER BY w.name, kgn.name; 

Całkowity czas trwania: 2156.740 ms. Dlaczego funkcja jest wykonywana dłużej niż to samo zapytanie? Dziękujemy za

+0

Czy możesz pokazać nam oba plany wykonania? (np. prześlij je do http://explain.depesz.com) –

+0

Jestem nieco uproszczonymi zapytaniami dla lepszej czytelności. Wyjaśnij wynik analizy dla funkcji: http://explain.depesz.com/s/AfeU i dla zapytania: http://explain.depesz.com/s/OKN Poza tym próbuję wyjaśnić w funkcji: http://explain.depesz.com/s/jxnb –

+0

Nie mogę tego teraz znaleźć, ale myślę, że kiedyś przeczytałem, że zapytanie w funkcji jest uruchamiane jako przygotowane zapytanie, co oznacza, że ​​plan jest wykonywany przed poznaniem parametrów, które mogą skutkuje niepoprawnymi planami (ale oszczędza narzutów podczas planowania kwerendy za każdym razem, gdy funkcja jest wykonywana). Jeśli chcesz wykonać zapytanie jako zapytanie dynamiczne za pomocą EXECUTE http://www.postgresql.org/docs/9.2/static/plpgsql-statements.html#PLPGSQL -STATEMENTS-EXECUTING-DYN, plan powinien zostać wykonany, gdy parametry są znany. – Eelke

Odpowiedz

4

Twoje zapytanie działa szybciej, ponieważ "zmienne" w rzeczywistości nie są zmienne - są to wartości statyczne (ciągi znaków IE w cudzysłowach). Oznacza to, że planista wykonania może wykorzystać indeksy. W ramach procedury składowanej zmienne są zmiennymi rzeczywistymi, a planista nie może przyjąć założeń dotyczących indeksów. Na przykład - możesz mieć częściowy indeks na requeststatushistory, gdzie "data" to < = "2012-12-31". Indeks może być używany tylko wtedy, gdy znane są 3 USD. Ponieważ może mieć datę z 2015 r., Indeks cząstkowy nie będzie miał żadnego zastosowania. W rzeczywistości byłoby to szkodliwe.

ja często skonstruować ciąg zasięgu moich funkcji, gdzie ja złączyć moje zmienne jako literały, a następnie wykonać funkcję używając coś jak następuje:

DECLARE 
    my_dynamic_sql TEXT; 
BEGIN 
    my_dynamic_sql := $$ 
     SELECT * 
     FROM my_table 
     WHERE $$ || quote_literal($3) || $$::TIMESTAMPTZ BETWEEN start_time 
                  AND end_time;$$; 

    /* You can only see this if client_min_messages = DEBUG */ 
    RAISE DEBUG '%', my_dynamic_sql; 
    RETURN QUERY EXECUTE my_dynamic_sql; 
END; 

Dynamiczny SQL jest bardzo przydatna, ponieważ rzeczywiście można uzyskać wyjaśnienia zapytania, gdy mam set client_min_messages=DEBUG;, mogę odrapać zapytanie z ekranu i wkleić je ponownie po EXPLAIN lub EXPLAIN ANALYZE i zobaczyć, co robi planista wykonania. Umożliwia to również konstruowanie bardzo różnych zapytań, w zależności od potrzeb, w celu optymalizacji pod kątem zmiennych (w razie potrzeby wyklucza niepotrzebne tabele) i utrzymania wspólnego interfejsu API dla klientów.

Być może pokusisz się o uniknięcie dynamicznego SQL ze strachu przed problemami z wydajnością (byłem na początku), ale będziesz zaskoczony, jak LITTLE czas poświęca się na planowanie w porównaniu do niektórych kosztów kilku skanów tabeli twoje siedmio-stołowe dołączenie!

Powodzenia!

Kontynuacja: Możesz także eksperymentować z Common Table Expressions (CTE) również pod kątem wydajności. Jeśli masz tabelę, która ma niski stosunek sygnału do szumu (ma wiele, wiele więcej rekordów niż w rzeczywistości chcesz zwrócić), wtedy CTE może być bardzo pomocne. PostgreSQL wykonuje CTE na początku zapytania i materializuje wynikowe wiersze w pamięci. Pozwala to na używanie tego samego zestawu wyników wiele razy i w wielu miejscach w zapytaniu. Korzyści mogą być naprawdę zaskakujące, jeśli zaprojektujesz je poprawnie.

sql_txt := $$ 
WITH my_cte as (
    select fk1 as moar_data 1 
     , field1 
     , field2 /*do not need all other fields taking up RAM!*/ 
    from my_table 
    where field3 between $$ || quote_literal(input_start_ts) || $$::timestamptz 
        and $$ || quote_literal(input_end_ts) || $$::timestamptz 
       ), 
     keys_cte as (select key_field 
        from big_look_up_table 
        where look_up_name = ANY($$ || 
         QUOTE_LITERAL(input_array_of_names) || $$::VARCHAR[]) 
       ) 
SELECT field1, field2, moar_data1, moar_data2 
FROM moar_data_table 
INNER JOIN my_cte 
    USING (moar_data1) 
WHERE moar_data_table.moar_data_key in (select key_field from keys_cte) $$; 

plan wykonania jest prawdopodobne, aby pokazać, że zdecyduje się użyć indeksu na moar_data_tale.moar_data_key. Wydaje się, że jest to sprzeczne z tym, co powiedziałem powyżej w mojej poprzedniej odpowiedzi - z wyjątkiem faktu, że wyniki keys_cte są zmaterializowane (i dlatego nie mogą być zmienione przez inną transakcję w stanie wyścigu) - masz własną małą kopię dane do wykorzystania w tym zapytaniu.

Och - i CTE mogą używać innych CTE, które są zadeklarowane wcześniej w tym samym zapytaniu. Użyłem tej "sztuczki" do zastąpienia sub-zapytań w bardzo złożonych połączeniach i zobaczyłem wielkie ulepszenia.

Happy Hacking!

+0

Dziękuję za szczegółową odpowiedź, działa naprawdę szybciej! –