每个函数都有一个波动性分类,其可能性为 VOLATILE
、STABLE
或 IMMUTABLE
。如果 CREATE FUNCTION
命令未指定类别,则默认值为 VOLATILE
。波动性类别是关于函数行为对优化器的承诺
VOLATILE
函数可以执行任何操作,包括修改数据库。它可以在对同一参数进行连续调用时返回不同的结果。优化器不会对这些函数的行为做出任何假设。使用不稳定函数的查询将在需要其值的每行重新评估该函数。
STABLE
函数不能修改数据库,并且保证对单个语句内所有行给出相同参数时返回相同的结果。此类别允许优化器将函数的多个调用优化为单个调用。尤其,在索引扫描条件中使用包含此类函数的表达式是安全的。(由于索引扫描只会评估一次比较值,而不会在每一行评估一次,因此在索引扫描条件中使用 VOLATILE
函数是无效的。)
IMMUTABLE
函数不能修改数据库,并且保证永久给定相同参数时返回相同的结果。此类别允许优化器在查询使用常量参数调用函数时对函数进行预评估。例如,像 SELECT ... WHERE x = 2 + 2
这样的查询可以立即简化为 SELECT ... WHERE x = 4
,因为整数加法运算符底层函数标记为 IMMUTABLE
。
为了获得最佳优化效果,您应该用对它们有效的波动性类别最严格的给您的函数贴上标签。
任何有副作用的函数必须标为 VOLATILE
,这样对它们的调用才不会被优化掉。即使没有副作用的函数也需要标为 VOLATILE
,如果其值在单个查询内可能更改;一些示例是 random()
、currval()
、timeofday()
。
另一个重要示例是 current_timestamp
函数系列符合 STABLE
,这是因为它们的值在事务中不会更改。
对于考虑直接执行的简单交互式查询来说,STABLE
和 IMMUTABLE
类别之间的差异很小:函数在计划期间执行一次还是在查询执行启动期间执行一次并无多大要紧。但如果计划之后保存并重新使用,则差别很大。将一个函数错误地标记为 IMMUTABLE
时,可能导致它在计划期间过早地折叠为常数,进而在随后的计划使用期间再次使用过时的值。在使用预处理语句或使用缓存计划的函数语言(如 PL/pgSQL)时,这是一种危险。
对于用 SQL 或任何标准过程语言编写的函数,由可变性类别决定的另一重要属性是调用函数的 SQL 命令所做的任何数据更改的可见性。VOLATILE
函数将看到此类更改,而 STABLE
或 IMMUTABLE
函数不会。此行为是使用 MVCC 的快照行为来实现的(请参阅 第 13 章):STABLE
和 IMMUTABLE
函数使用在调用查询开始时建立的快照,而 VOLATILE
函数在执行的每个查询开始时获取新快照。
用 C 编写的函数可以按它们想要的方式管理快照,但通常最好也让 C 函数采用此方式。
由于此快照行为,即使仅包含 SELECT
命令的函数也可以安全地标记为 STABLE
,即使它从可能被并发查询修改的表中进行选择。PostgreSQL 将使用为调用查询建立的快照执行 STABLE
函数的所有命令,因此它将在整个查询中看到数据库的固定视图。
相同的快照行为用于 IMMUTABLE
函数中的 SELECT
命令。通常不建议在 IMMUTABLE
函数中从数据库表中进行选择,因为如果表内容发生更改,不可变性将被破坏。不过,PostgreSQL 并没有强制执行这一点。
一个常见的错误是将函数标记为 IMMUTABLE
而其结果取决于一个配置参数。例如,操纵时间戳的函数很可能具有依赖于 TimeZone 设置的结果。为了安全起见,此类函数应标记为 STABLE
。
PostgreSQL 要求 STABLE
和 IMMUTABLE
函数不包含 SELECT
以外的 SQL 命令,以防止数据修改。(这不是一项完全防弹的测试,因为此类函数仍可调用修改数据库的 VOLATILE
函数。如果您这样做,您会发现 STABLE
或 IMMUTABLE
函数不会注意到所调用函数应用的数据库更改,因为它们对快照是隐藏的。)