40.11. Информация для оптимизации функций
Сама по себе функция для СУБД является просто «чёрным ящиком», о поведении которого известно очень мало. Это означает, что запросы, вызывающие функции, могут выполняться гораздо менее эффективно, чем могли бы в теории. Поэтому имеется возможность сообщить планировщику дополнительные сведения о функции, которые помогут ему оптимизировать вызовы функций.
Некоторые основные факты передаются декларативным образом в команде CREATE FUNCTION; в их числе один из самых значимых — характеристика изменчивости (IMMUTABLE, STABLE или VOLATILE). Создавая любую функцию, очень важно правильно определить эту характеристику. Также может определяться характеристика распараллеливания (PARALLEL UNSAFE, PARALLEL RESTRICTED или PARALLEL SAFE), если вы рассчитываете, что эта функция будет использоваться в распараллеливаемых запросах. Кроме того, для функции может задаваться примерная стоимость выполнения и/или оценка количества строк, выдаваемого функцией, возвращающей множество. Однако декларативный способ описания двух последних фактов позволяет задать только некоторое постоянное значение, а это полезно далеко не всегда.
Но имеется также возможность связать вспомогательную функцию для планировщика с вызываемой из SQL функцией (она будет целевой функцией для первой), и передать через неё ту информацию о целевой функции, которая слишком сложна для представления в декларативном виде. Вспомогательные функции для планировщика должны быть написаны на C (хотя язык целевых функций может быть любым), что переводит их в категорию расширенных возможностей, и разрабатывать их будут относительно немногие пользователи.
Вспомогательная функция должна иметь в SQL такую сигнатуру:
supportfn(internal) returns internal
Она связывается с целевой функцией с помощью указания SUPPORT в команде, создающей целевую функцию.
Здесь даётся общее представление о том, что могут делать вспомогательные функции для планировщика. Множество запросов к вспомогательным функциям расширяемое, так что в будущих версиях у них могут появиться и другие возможности.
Некоторые вызовы функций можно упростить во время планирования, в зависимости от особенностей функции. Например, вызов int4mul(n, 1) можно свести просто к n. Преобразование такого рода может выполняться вспомогательной функцией, если она обрабатывает запросы типа SupportRequestSimplify. Эта вспомогательная функция будет вызываться для каждого экземпляра вызова целевой функции, найденного в дереве разобранного запроса. Если она обнаруживает, что этот конкретный вызов можно упростить и привести к другому виду, она может построить и возвратить другое дерево с изменённым выражением. Это будет автоматически работать и для операторов, основанных на функциях, — в данном примере n * 1 будет также упрощено до n. (Но заметьте, что это просто иллюстрация; конкретно эту оптимизацию стандартный Postgres Pro не производит). При этом не гарантируется, что Postgres Pro никогда не вызовет целевую функцию в случаях, которые может упростить вспомогательная функция. С учётом этого важно обеспечить строгую идентичность упрощённого выражения фактическому выполнению целевой функции.
Для целевой функции, возвращающей значение boolean, часто бывает полезно оценить, какой процент строк будет выбран предложением WHERE, в котором вызывается эта функция. Эту оценку позволяет получить вспомогательная функция, обрабатывающая запросы типа SupportRequestSelectivity.
Если алгоритм работы целевой функции значительно меняется в зависимости от её аргументов, для неё может иметь смысл вычислять переменную оценку стоимости. Это можно сделать, реализовав вспомогательную функцию, которая будет обрабатывать запросы типа SupportRequestCost.
Для целевой функции, возвращающей множество, часто полезно иметь переменную оценку числа выдаваемых строк. Реализовать это позволяет вспомогательная функция, обрабатывающая запросы типа SupportRequestRows.
Для целевой функции, возвращающей значение boolean, может существовать возможность преобразовать вызов функции в условии WHERE в предложение(я) с индексируемыми операторами. Преобразованные предложения должны быть в точности идентичны условию функции либо могут быть несколько менее строгими (то есть они могут принимать некоторые значения, не удовлетворяющие условию с функцией). В последнем случае условие с индексом считается неточным; оно может использоваться для поиска по индексу, но для каждой строки, полученной при таком поиске, должна вызываться функция, чтобы точно определить, удовлетворяет ли строка условию WHERE. Для создания таких условий вспомогательная функция должна обрабатывать запросы типа SupportRequestIndexCondition.
40.11. Function Optimization Information
By default, a function is just a “black box” that the database system knows very little about the behavior of. However, that means that queries using the function may be executed much less efficiently than they could be. It is possible to supply additional knowledge that helps the planner optimize function calls.
Some basic facts can be supplied by declarative annotations provided in the CREATE FUNCTION command. Most important of these is the function's volatility category (IMMUTABLE, STABLE, or VOLATILE); one should always be careful to specify this correctly when defining a function. The parallel safety property (PARALLEL UNSAFE, PARALLEL RESTRICTED, or PARALLEL SAFE) must also be specified if you hope to use the function in parallelized queries. It can also be useful to specify the function's estimated execution cost, and/or the number of rows a set-returning function is estimated to return. However, the declarative way of specifying those two facts only allows specifying a constant value, which is often inadequate.
It is also possible to attach a planner support function to an SQL-callable function (called its target function), and thereby provide knowledge about the target function that is too complex to be represented declaratively. Planner support functions have to be written in C (although their target functions might not be), so this is an advanced feature that relatively few people will use.
A planner support function must have the SQL signature
supportfn(internal) returns internal
It is attached to its target function by specifying the SUPPORT clause when creating the target function.
Here we provide an overview of what planner support functions can do. The set of possible requests to a support function is extensible, so more things might be possible in future versions.
Some function calls can be simplified during planning based on properties specific to the function. For example, int4mul(n, 1) could be simplified to just n. This type of transformation can be performed by a planner support function, by having it implement the SupportRequestSimplify request type. The support function will be called for each instance of its target function found in a query parse tree. If it finds that the particular call can be simplified into some other form, it can build and return a parse tree representing that expression. This will automatically work for operators based on the function, too — in the example just given, n * 1 would also be simplified to n. (But note that this is just an example; this particular optimization is not actually performed by standard Postgres Pro.) We make no guarantee that Postgres Pro will never call the target function in cases that the support function could simplify. Ensure rigorous equivalence between the simplified expression and an actual execution of the target function.
For target functions that return boolean, it is often useful to estimate the fraction of rows that will be selected by a WHERE clause using that function. This can be done by a support function that implements the SupportRequestSelectivity request type.
If the target function's run time is highly dependent on its inputs, it may be useful to provide a non-constant cost estimate for it. This can be done by a support function that implements the SupportRequestCost request type.
For target functions that return sets, it is often useful to provide a non-constant estimate for the number of rows that will be returned. This can be done by a support function that implements the SupportRequestRows request type.
For target functions that return boolean, it may be possible to convert a function call appearing in WHERE into an indexable operator clause or clauses. The converted clauses might be exactly equivalent to the function's condition, or they could be somewhat weaker (that is, they might accept some values that the function condition does not). In the latter case the index condition is said to be lossy; it can still be used to scan an index, but the function call will have to be executed for each row returned by the index to see if it really passes the WHERE condition or not. To create such conditions, the support function must implement the SupportRequestIndexCondition request type.