Функция Postgresql выполняется намного дольше, чем тот же запрос

Я использую PostgreSQL 9.2.9 и имею следующую проблему.

есть функция:

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 <= 
        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( 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 = 
    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);

Общее время выполнения: 242805.085 МС. Но запрос из тела функции выполняется намного быстрее:

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;

Общее время выполнения: 2156.740 МС. Почему функция выполняется дольше, чем тот же запрос? Спасибо

1 ответов


ваш запрос работает быстрее, потому что" переменные " на самом деле не являются переменными-они являются статическими значениями (т. е. строками в кавычках). Это означает, что планировщик выполнения может использовать индексы. В рамках хранимой процедуры переменные являются фактическими переменными, и планировщик не может делать предположения об индексах. Например-у вас может быть частичный индекс на requeststatushistory, где "дата"

Я часто строю строку в своих функциях, где я объединяю свои переменные как литералы, а затем выполняю функцию, используя что-то вроде следующего:

DECLARE
    my_dynamic_sql TEXT;
BEGIN
    my_dynamic_sql := $$
        SELECT * 
        FROM my_table 
        WHERE $$ || quote_literal() || $$::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;

динамический SQL очень полезен, потому что вы действительно можете получить объяснение запроса, когда у меня есть set client_min_messages=DEBUG; Я могу очистить запрос от экрана и вставить его обратно после EXPLAIN или EXPLAIN ANALYZE и видим, что исполнение планировщик делает. Это также позволяет создавать очень разные запросы, необходимые для оптимизации переменных (т. е. исключать ненужные таблицы, если это необходимо) и поддерживать общий API для ваших клиентов.

у вас может возникнуть соблазн избежать динамического SQL из-за боязни проблем с производительностью (я был сначала), но вы будете поражены тем, как мало времени тратится на планирование по сравнению с некоторыми из стоимости нескольких сканирований таблицы на вашем семи-таблице присоединиться!

хорошее удачи!

Последующие действия: вы можете экспериментировать с Common Table Expressions (CTEs) для представления также. Если у вас есть таблица с низким отношением сигнал / шум (имеет много, много больше записей, чем вы на самом деле хотите вернуть), то CTE может быть очень полезным. PostgreSQL выполняет CTEs в начале запроса и материализует результирующие строки в памяти. Это позволяет использовать один и тот же результирующий набор несколько раз и в нескольких местах в запросе. Польза действительно может быть удивительной, если вы дизайн-это правильно.

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) $$;

план выполнения, вероятно, покажет, что он выбирает использовать индекс на moar_data_tale.moar_data_key. Это, по-видимому, идет вразрез с тем, что я сказал выше в моем предыдущем ответе - за исключением того факта, что keys_cte результаты материализуются (и поэтому не могут быть изменены другой транзакцией в условиях гонки)-у вас есть своя небольшая копия данных для использования в этом запросе.

Oh - и CTEs могут использовать другие CTEs, которые объявлены ранее в том же запрос. Я использовал этот "трюк" для замены подзапросов в очень сложных соединениях и видел большие улучшения.

Всего Доброго!