在默认情况下,函数对数据库系统而言只是一个“黑盒”,优化器对其内部运行行为知之甚少。然而,这意味着使用该函数的查询执行效率可能远低于预期。可以通过提供额外的元数据知识,帮助查询规划器对函数调用进行深度优化。
部分基础特征可以通过 CREATE FUNCTION 命令中提供的声明式注解来指定。其中最关键的是函数的 易失性分类(IMMUTABLE、STABLE 或 VOLATILE);在定义函数时应始终注意准确指定。如果您希望在并行查询中使用该函数,还必须显式指定其并行安全属性(PARALLEL UNSAFE、PARALLEL RESTRICTED 或 PARALLEL SAFE)。为函数指定预估执行代价(COST)以及/或集合返回函数预估返回的行数(ROWS)同样大有裨益。然而,以声明方式指定这两种特征仅允许指定固定常量值,这在许多复杂场景下往往不够精准。
还可以为可供 SQL 调用的函数(称为其目标函数 target function)附加一个规划器支持函数(planner support function),从而提供那些过于复杂而无法通过简单声明表达的优化指导。规划器支持函数必须使用 C 语言编写(尽管其目标函数可以使用其他语言编写),因此这是一项高级特性。
规划器支持函数必须具备如下 SQL 签名:
supportfn(internal) returns internal
在创建目标函数时,通过指定 SUPPORT 子句将其附加到目标函数上。
规划器支持函数的详细 API 可以在 PostgreSQL 源码中的文件 src/include/nodes/supportnodes.h 中找到。此处我们仅概述规划器支持函数能够完成的功能。支持函数所处理的请求类型集合是可扩展的,未来版本可能会增加更多优化能力。
基于特定函数的数学或语义属性,某些函数调用可以在规划期间直接进行代数化简。例如,int4mul(n, 1) 可以直接简化为 n。这种类型的表达式变换可以通过让规划器支持函数实现 SupportRequestSimplify 请求类型来完成。对于在查询解析树中发现的每个目标函数实例,都会调用一次该支持函数。如果发现特定调用可以简化为其他形式,它可以构建并返回代表该简化表达式的语法解析树。这对于基于该函数的运算符同样自动生效——在上述示例中,n * 1 也会被自动简化为 n(但请注意这只是示意性例子,标准 PostgreSQL 实际上并未内置该特定简化)。官方不保证在支持函数可以化简的情况下系统绝不调用目标函数。开发者必须确保简化后的表达式与目标函数的实际物理执行在语义上保持严格等价。
对于返回 boolean 的目标函数,预估使用该函数的 WHERE 子句所能筛选出的数据行比例(选择率 Selectivity)通常极具价值。这可以通过让支持函数实现 SupportRequestSelectivity 请求类型来完成。
如果目标函数的执行时间高度依赖于输入数据,为其提供非固定的动态执行代价预估会非常有用。这可以通过让支持函数实现 SupportRequestCost 请求类型来达成。
对于返回集合的目标函数,为其提供预期返回行数的动态预估同样大有裨益。这可以通过让支持函数实现 SupportRequestRows 请求类型来达成。
对于返回 boolean 的目标函数,还可以将出现在 WHERE 中的函数调用转换为一个或多个支持索引扫描的运算符子句。转换后的子句可能与函数的原始条件完全等价,也可能稍弱(即可能接受某些函数条件本身并不满足的值)。在后一种情况下,该索引条件被称为有损的(lossy);它仍可用于快速过滤执行索引扫描,但在返回每个候选数据行后,必须再次执行该函数调用以准确验证是否真正满足 WHERE 过滤条件。要生成此类索引条件,支持函数必须实现 SupportRequestIndexCondition 请求类型。