Почему PostgreSQL по-разному обрабатывает мой запрос в функции? - PullRequest
3 голосов
/ 16 февраля 2012

У меня очень простой запрос, который не намного сложнее, чем:

select *
from table_name
where id = 1234

... для запуска требуется менее 50 миллисекунд.

Взял этот запрос и поместил в функцию:

CREATE OR REPLACE FUNCTION pie(id_param integer)
RETURNS SETOF record AS
$BODY$
BEGIN
    RETURN QUERY SELECT *
         FROM table_name
         where id = id_param;
END
$BODY$
LANGUAGE plpgsql STABLE;

Эта функция при выполнении select * from pie(123); занимает 22 секунды.

Если вместо id_param я жестко кодирую целое число, функция выполняется менее чем за 50 миллисекунд.

Почему тот факт, что я использую параметр в операторе where, заставляет мою функцию работать медленно?


Изменить, чтобы добавить конкретный пример:

CREATE TYPE test_type AS (gid integer, geocode character varying(9))

CREATE OR REPLACE FUNCTION geocode_route_by_geocode(geocode_param character)
  RETURNS SETOF test_type AS
$BODY$
BEGIN
RETURN QUERY EXECUTE
    'SELECT     gs.geo_shape_id AS gid,     
        gs.geocode
    FROM geo_shapes gs
    WHERE geocode = $1
    AND geo_type = 1 
    GROUP BY geography, gid, geocode' USING geocode_param;
END;

$BODY$
  LANGUAGE plpgsql STABLE;
ALTER FUNCTION geocode_carrier_route_by_geocode(character)
  OWNER TO root;

--Runs in 20 seconds
select * from geocode_route_by_geocode('999xyz');

--Runs in 10 milliseconds
SELECT  gs.geo_shape_id AS gid,     
        gs.geocode
    FROM geo_shapes gs
    WHERE geocode = '9999xyz'
    AND geo_type = 1 
    GROUP BY geography, gid, geocode

1 Ответ

7 голосов
/ 16 февраля 2012

Обновление в PostgreSQL 9.2

Произошло значительное улучшение, я цитирую примечания к выпуску здесь :

Разрешить планировщику для создания пользовательских планов для конкретного параметра значения даже при использовании подготовленных высказываний (Том Лейн)

В прошлом у подготовленного заявления всегда был один "общий" план который был использован для всех значений параметров, что часто было много уступает планам, используемым для неподготовленных заявлений, содержащих явные значения констант. Теперь планировщик пытается создать пользовательский планы для конкретных значений параметров. Общий план будет использоваться только после пользовательских планов неоднократно доказано, что не дают никакой пользы. это изменение должно устранить штрафы за производительность, ранее замеченные из использование подготовленных высказываний (включая нединамические высказывания в PL / PgSQL). * * 1 010


Оригинальный ответ для PostgreSQL 9.1 или старше

Функции plpgsql имеют тот же эффект, что и оператор PREPARE: запросы анализируются, а план запроса кэшируется.

Преимущество заключается в том, что некоторые затраты сохраняются для каждого вызова.
Недостатком является то, что план запроса не оптимизирован для конкретных значений параметров, с которыми он вызывается.

Для запросов к таблицам с равномерным распределением данных это, как правило, не представляет проблемы, и функции PL / pgSQL будут работать несколько быстрее, чем необработанные запросы SQL или функции SQL. Но если ваш запрос может использовать определенные индексы в зависимости от фактических значений в предложении WHERE или, в более общем случае, выбрать лучший план запроса для определенных значений, вы можете получить неоптимальный план запроса. Попробуйте использовать функцию SQL или используйте динамический SQL с EXECUTE, чтобы принудительно перепланировать запрос для каждого вызова. Может выглядеть так:

CREATE OR REPLACE FUNCTION pie(id_param integer)
RETURNS SETOF record AS
$BODY$
BEGIN        
    RETURN QUERY EXECUTE
        'SELECT *
         FROM   table_name
         where  id = $1'
    USING id_param;
END
$BODY$
LANGUAGE plpgsql STABLE;

Редактировать после комментария:

Если этот вариант не меняет время выполнения, должны быть и другие факторы, которые вы могли пропустить или не упомянуть. Другая база данных? Разные значения параметров? Вы должны будете опубликовать более подробную информацию.

Я добавляю цитату из руководства , чтобы поддержать мои приведенные выше утверждения:

EXECUTE с простой константой командной строки и некоторым ИСПОЛЬЗОВАНИЕМ Параметры, как в первом примере выше, функционально эквивалентны просто написать команду прямо в PL / pgSQL и позволить замена переменных PL / pgSQL происходит автоматически. Важным отличием является то, что EXECUTE будет перепланировать команду на каждом выполнение, генерация плана, который является специфическим для текущего параметра ценности; тогда как PL / pgSQL обычно создает общий план и кэширует его для повторного использования. В ситуациях, когда лучший план сильно зависит от значения параметров EXECUTE могут быть значительно быстрее; в то время как когда план не чувствителен к значениям параметров, перепланирование будет отходы.

...