第 16 章 触发器
PostgreSQL 有多种服务器端函数接口。服务器端函数可以用 SQL、C 或任何已定义的过程语言编写。触发器函数可以用 C 和大多数过程语言编写,但不能用 SQL 编写。注意,当前版本不支持语句级触发器事件。目前可以把 BEFORE 或 AFTER 指定在一个元组的 INSERT、DELETE 或 UPDATE 上作为触发器事件。
16.1. 触发器定义 #
一旦触发器事件发生,触发器管理器(由执行器调用)就会建立一个
TriggerData 信息结构(下文描述),并调用触发器函数来处理该事件。
必须先定义触发器函数,之后才能创建触发器本身。触发器函数必须声明为不接受参数并返回类型 trigger 的函数。(触发器函数通过一个 TriggerData
结构接收输入,而不是以普通函数参数的形式。)如果函数用 C 编写,它必须使用“版本 1”函数管理器接口。
创建触发器的语法是:
CREATE TRIGGERtrigger[ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ] ONrelationFOR EACH [ ROW | STATEMENT ] EXECUTE PROCEDUREprocedure(args);
其中各个参数为:
-
trigger 触发器的名称必须与同一表上的所有其他触发器不同。如果将来需要删除该触发器,就要用到这个名称。
- BEFORE
AFTER 决定函数是在事件之前还是之后被调用。
- INSERT
DELETE
UPDATE 命令的下一个元素决定哪些事件会触发该函数。可以用 OR 分隔指定多个事件。
relation关系名指出该事件作用于哪个表。
- ROW
STATEMENT FOR EACH 子句决定触发器是针对每个受影响的行触发,还是在整条语句完成之前(或之后)触发。目前只支持 ROW 的情形。
procedure过程名是要被调用的函数。
args通过
TriggerData结构传给函数的参数。它要么为空,要么是一个由一个或多个简单字面常量组成的列表(它们将作为字符串传给函数)。在触发器定义中包含参数,是为了让需求相似的不同触发器能够调用同一个函数。举例来说,可以有一个通用触发器函数,它接受两个列名作为参数,把当前用户写入其中一个列,把当前时间戳写入另一个列。只要编写得当,这个触发器函数就应独立于它所作用的具体表。因此,同一个函数可用于任何具有适当字段的表上的 INSERT 事件,例如自动跟踪某个事务表中记录的创建。如果把它定义成 UPDATE 触发器,还可以用来跟踪最近更新事件。
触发器函数向调用它的执行器返回一个 HeapTuple。该返回值对于在操作之后(AFTER)触发的触发器会被忽略,但它允许 BEFORE 触发器:
返回一个
NULL指针,以跳过对当前元组的操作(这样该元组就不会被插入/更新/删除)。仅对于 INSERT 和 UPDATE 触发器,返回的元组会成为将要插入的元组,或替换正在更新的元组。这使得触发器函数可以修改即将插入或更新的行。
不打算引发上述任何一种行为的 BEFORE 触发器,必须注意把传给它的同一个 NEW 元组原样返回。
注意,CREATE TRIGGER 处理器不执行任何初始化。这一点将来可能会改变。
如果为同一关系上的同一事件定义了多个触发器,它们会按名称的字母顺序触发。对于 BEFORE 触发器,每个触发器返回的、可能已经被修改过的元组都会成为下一个触发器的输入。如果任何 BEFORE
触发器返回 NULL,则会放弃该操作,并且后续触发器不会再触发。
如果触发器函数执行 SQL 查询(使用 SPI),那么这些查询可能会再次触发触发器。这就是所谓的级联触发器。对级联层数没有直接限制。级联还有可能导致同一个触发器被递归调用 —— 例如,一个 INSERT 触发器可能执行一条向同一个表再插入一个元组的查询,从而导致该 INSERT 触发器再次触发。因此,在这种场景下,避免无限递归是触发器编写者自己的责任。