每个函数都有一个波动性分类,可能性为VOLATILE
、STABLE
或IMMUTABLE
。VOLATILE
是默认值,如果CREATE FUNCTION
命令未指定类别。波动性类别是对优化器关于函数行为的承诺
一个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
时将其标记为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
函数不会注意到被调用函数应用的数据库更改,因为它们被隐藏在其快照中。)