所有在 PL/pgSQL 语句里使用的表达式都是用服务器的普通 SQL 执行器进行处理的。实际上,类似下面的查询
SELECT expression
是使用 SPI 管理器执行的。在计算之前,出现 PL/pgSQL 变量标识符的地方先被参数代替,然后变量的实际值放在参数数组里传递给执行器。这样就允许 SELECT 的执行计划只需要准备一次,并且在随后的计算中复用。
PostgreSQL 的主分析器做的类型检查对常量数值的代换有一些副作用。详细说来就是下面这两个函数做的事情有些区别:
CREATE FUNCTION logfunc1(logtxt text) RETURNS timestamp AS $$ BEGIN INSERT INTO logtable VALUES (logtxt, 'now'); RETURN 'now'; END; $$ LANGUAGE plpgsql;
和
CREATE FUNCTION logfunc2(logtxt text) RETURNS timestamp AS $$ DECLARE curtime timestamp; BEGIN curtime := 'now'; INSERT INTO logtable VALUES (logtxt, curtime); RETURN curtime; END; $$ LANGUAGE plpgsql;
在 logfunc1
的实例里,PostgreSQL 的主分析器在为 INSERT 准备执行计划的时候知道字符串 'now' 应该解释成 timestamp 类型,因为 logtable
的目标字段就是该类型。所以,它会在这个时候从这个字符串中计算一个常量,然后在该服务器的整个生存期中的所有 logfunc1
调用中使用这个常量。不消说,这可不是程序员想要的。
在 logfunc2
里,PostgreSQL 的主分析器并不知道 'now' 应该转换成什么类型,因此它返回一个包含字符串 now 的类型为 text 的数据值。在随后给局部变量 curtime 赋值时,PL/pgSQL 解释器通过调用 text_out
和 timestamp_in
把这个字符串转换成 timestamp 类型的变量。因此,计算出的时戳就会按照程序员希望的那样在每次执行的时候都更新。
记录变量的易变性在这种结合上提出了一个问题。当一个记录变量在语句或者表达式中使用时,该字段的数据类型在同一个表达式的不同调用期间不能修改,因为该表达式准备使用的是运行第一次到达该表达式时出现的数据类型。在写处理多个表的事件的触发器过程的时候一定要把这个记住。必要时可以用 EXECUTE 绕开这个问题。