Почему PostgreSQL вызывает мою функцию STABLE/IMMUTABLE несколько раз?

Я пытаюсь оптимизировать сложный запрос в PostgreSQL 9.1.2, который вызывает некоторые функции. Эти функции отмечены STABLE или IMMUTABLE и вызываются несколько раз с теми же аргументами в запросе. Я предположил, что PostgreSQL будет достаточно умным, чтобы называть только один раз для каждого набора входов - в конце концов, что точка STABLE и IMMUTABLE, не так ли? Но, похоже, функции вызывается несколько раз. Я написал простую функцию для проверки этого, что подтверждает это:

CREATE OR REPLACE FUNCTION test_multi_calls1(one integer)
RETURNS integer
AS $BODY$
BEGIN
    RAISE NOTICE 'Called with %', one;
    RETURN one;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE;


WITH data AS
(
    SELECT 10 AS num
    UNION ALL SELECT 10
    UNION ALL SELECT 20
)
SELECT test_multi_calls1(num)
FROM data;

Вывод:

NOTICE:  Called with 10
NOTICE:  Called with 10
NOTICE:  Called with 20

Почему это происходит и как я могу заставить его выполнять функцию только один раз?

Ответы

Ответ 1

Следующее расширение вашего тестового кода является информативным:

CREATE OR REPLACE FUNCTION test_multi_calls1(one integer)
RETURNS integer
AS $BODY$
BEGIN
    RAISE NOTICE 'Immutable called with %', one;
    RETURN one;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE;
CREATE OR REPLACE FUNCTION test_multi_calls2(one integer)
RETURNS integer
AS $BODY$
BEGIN
    RAISE NOTICE 'Volatile called with %', one;
    RETURN one;
END;
$BODY$ LANGUAGE plpgsql VOLATILE;

WITH data AS
(
    SELECT 10 AS num
    UNION ALL SELECT 10
    UNION ALL SELECT 20
)
SELECT test_multi_calls1(num)
FROM data
where test_multi_calls2(40) = 40
and test_multi_calls1(30) = 30

ВЫВОД:

NOTICE:  Immutable called with 30
NOTICE:  Volatile called with 40
NOTICE:  Immutable called with 10
NOTICE:  Volatile called with 40
NOTICE:  Immutable called with 10
NOTICE:  Volatile called with 40
NOTICE:  Immutable called with 20

Здесь мы видим, что в то время как в списке выбора неизменяемая функция вызывалась несколько раз, в предложении where она вызывалась один раз, а volatile - трижды.

Важно то, что PostgreSQL будет вызывать функцию STABLE или IMMUTABLE только один раз с теми же данными - ваш пример ясно показывает, что это не так - это может быть вызвано только один раз. Или, возможно, он будет вызывать его дважды, когда ему придется вызывать летучую версию 50 раз и т.д.

Существуют разные способы, с помощью которых можно использовать преимущества стабильности и неизменности при различных затратах и ​​выгодах. Чтобы предоставить вид сохранения, вы предлагаете, чтобы он делал с помощью списков выбора, он должен был кэшировать результаты, а затем искать каждый аргумент (или список аргументов) в этом кеше перед возвратом кэшированного результата или функции вызова в кеш -Мисс. Это было бы дороже, чем вызов вашей функции, даже в случае, когда был высокий процент кеш-хитов (может быть 0% кеш-хитов, что означает, что эта оптимизация сделала дополнительную работу абсолютно без выигрыша). Он может хранить, возможно, только последний параметр и результат, но опять же это может быть совершенно бесполезно.

Это особенно важно, учитывая, что стабильные и неизменные функции часто являются самыми легкими функциями.

Однако с предложением where неизменность test_multi_calls1 позволяет PostgreSQL реально реструктурировать запрос из простого значения данного SQL:

Для каждой строки вычисляйте test_multi_calls1 (30), и если результат равный 30 продолжить обработку рассматриваемой строки

Для другого плана запроса полностью:

Вычислить test_multi_calls1 (30), и если он равен 30, тогда продолжите с запросом, иначе верните результат с нулевой строкой без любое дальнейшее вычисление

Это то, что PostgreSQL использует для STABLE и IMMUTABLE - не для кэширования результатов, а для перезаписи запросов в разные запросы, которые более эффективны, но дают те же результаты.

Обратите внимание также, что test_multi_calls1 (30) вызывается перед test_multi_calls2 (40) независимо от того, какой порядок они отображаются в предложении where. Это означает, что если первый вызов не приводит к возврату строк (замените = 30 на = 31 для проверки), тогда функция volatile не будет вызываться вообще - снова независимо от того, на какой стороне находится and.

Этот особый вид переписывания зависит от неизменности или стабильности. С where test_multi_calls1(30) != num запрос переписывания будет выполняться для неизменяемых, но не для простых стабильных функций. С where test_multi_calls1(num) != 30 это не произойдет вообще (несколько вызовов), хотя возможны и другие оптимизации:

При сканировании индексов могут использоваться выражения, содержащие только функции STABLE и IMMUTABLE. Выражения, содержащие функции VOLATILE, не могут. Количество вызовов может или не может уменьшаться, но гораздо важнее то, что результаты вызовов будут использоваться гораздо более эффективным способом в остальной части запроса (это действительно важно для больших таблиц, но тогда это может сделать массивный разница).

В целом, не думайте о категориях волатильности в терминах memoisation, а скорее в плане предоставления возможности планировщика запросов PostgreSQL для реструктуризации целых запросов способами, логически эквивалентными (одни и те же результаты), но гораздо более эффективными.

Ответ 2

В соответствии с документацией IMMUTABLE функции возвращают то же значение, учитывая те же аргументы. Поскольку вы кормите динамические аргументы (и даже не один раз), оптимизатор не имеет оснований полагать, что он получит те же результаты и, следовательно, вызовет функцию. Лучше qustion: почему ваш запрос вызывает функцию несколько раз, если это не нужно?