每一个函数都有一个易变性分类,可能是VOLATILE
、STABLE
或者IMMUTABLE
。
如果 CREATE FUNCTION
命令没有指定一个分类,则默认是VOLATILE
。
易变性分类是给优化器的关于该函数行为的一种承诺:
一个VOLATILE
函数可以做任何事情,包括修改数据库。在
使用相同的参数连续调用时,它能返回不同的结果。优化器不会对这类函
数的行为做任何假定。在每一行需要 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 的快照
行为(见Chapter 14)实现:STABLE
和
IMMUTABLE
函数使用一个在调用查询开始时建立的快照,而
VOLATILE
函数在它们执行的每一个查询的开始都获得一个新鲜
的快照。
用 C 编写的函数按照它们自己需要的方式管理快照,但是通常最好 让 C 函数也按照上面的方式来。
由于这种快照行为,一个只包含SELECT
命令的函数可以被
安全地标记为STABLE
,即便它选择的表可能正在被并发查询所
修改。LightDB将使用为调用查询所
建立的快照来执行STABLE
函数中的所有命令,因此它将在整个
查询期间看到一种数据库的固定视图。
对IMMUTABLE
函数中的SELECT
使用了相同
的快照行为。通常在一个IMMUTABLE
函数中从数据库表选择是
不明智的,因为如果表内容变化就会破坏不变性。不过,
LightDB不会强制不让你这样做。
一种常见的错误是当一个函数的结果依赖于一个配置参数时把它标记为
IMMUTABLE
。例如,一个操纵时间戳的函数有可能结果
依赖于TimeZone设置。为了安全起见,这类
函数应该被标记为STABLE
。
LightDB要求STABLE
和IMMUTABLE
函数中不包含非SELECT
的 SQL 命令以阻止数据修改(这也不是完全万无一失,因为这类函数还可以
调用修改数据库的VOLATILE
函数。如果那样做,你将发现
该STABLE
或IMMUTABLE
函数不会发现由被调
用函数所作的数据库改变,因为它们对它的快照不可见)。