Ответ 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 для реструктуризации целых запросов способами, логически эквивалентными (одни и те же результаты), но гораздо более эффективными.