Почему PostgreSQL вызывает мою функцию STABLE / IMMUTABLE несколько раз? - PullRequest
16 голосов
/ 16 декабря 2011

Я пытаюсь оптимизировать сложный запрос в 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

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

Ответы [ 2 ]

24 голосов
/ 16 декабря 2011

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

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

OUTPUT:

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 раз и т. Д.

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

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

С помощью whereТем не менее, в соответствии с предложением неизменность test_multi_calls1 позволяет PostgreSQL фактически реструктурировать запрос из простого значения заданного SQL:

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

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

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

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

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

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

0 голосов
/ 16 декабря 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...