第 35 章 触发器
本章提供有关编写触发器函数的一般信息。触发器函数可以用大多数可用的过程语言编写,包括 PL/pgSQL(第 38 章)、 PL/Tcl(第 39 章)、 PL/Perl(第 40 章)和 PL/Python(第 41 章)。读完本章后,你还应查阅所用过程语言对应的章节,了解用该语言编写触发器的语言相关细节。
也可以用 C 编写触发器函数,不过大多数人会觉得使用某种过程语言更容易。目前还不能用普通 SQL 函数语言编写触发器函数。
35.1. 触发器行为概述 #
触发器是这样一种规定:每当执行某种特定类型的操作时,数据库就应自动执行一个特定的函数。触发器可以定义为在任何
INSERT、UPDATE 或
DELETE 操作之前或之后执行,执行方式既可以是每个被修改的行一次,也可以是每条 SQL
语句一次。一旦触发器事件发生,触发器函数就会在恰当的时机被调用以处理该事件。
必须先定义触发器函数,之后才能创建触发器本身。触发器函数必须声明为不接受参数并返回类型 trigger 的函数。(触发器函数通过特殊传入的
TriggerData 结构体接收输入,而不是通过普通函数参数。)
创建出合适的触发器函数后,就可以使用CREATE TRIGGER创建触发器。同一个触发器函数可以供多个触发器使用。
PostgreSQL 同时提供每行触发器和每语句触发器。对于每行触发器,触发它的语句所影响的每一行都会调用一次触发器函数。相反,每语句触发器只会在执行相应语句时调用一次,不管该语句影响了多少行。特别是,即使某个语句一行也没有影响,仍会执行所有适用的每语句触发器。这两类触发器有时也分别称为行级触发器和语句级触发器。
触发器还分为之前触发器和之后触发器。语句级 BEFORE 触发器自然在语句开始做任何事之前触发,而语句级 AFTER 触发器在语句的最终末尾触发。行级 BEFORE 触发器在对某个特定行执行操作之前立即触发,而行级 AFTER 触发器在语句末尾触发(但早于任何语句级 AFTER 触发器)。
由每语句触发器调用的触发器函数应始终返回
NULL。由每行触发器调用的触发器函数则可以根据需要向调用它的执行器返回一个表行(即类型
HeapTuple 的值)。在操作之前触发的行级触发器可以作出如下选择:
它可以返回
NULL,以跳过对当前行的操作。这会指示执行器不要执行引发该触发器的行级操作(即对某个具体表行的插入或修改)。仅对于行级
INSERT和UPDATE触发器,返回的行会成为将要插入的行,或替换正在更新的行。这使得触发器函数可以修改即将插入或更新的行。
不打算引发上述任何一种行为的行级 BEFORE 触发器,必须注意把传入的行原样作为结果返回(也就是说,对于
INSERT 和 UPDATE 触发器返回 NEW 行,对于
DELETE 触发器返回
OLD 行)。
对于在操作之后触发的行级触发器,其返回值会被忽略,因此它们可以返回
NULL。
如果为同一关系上的同一事件定义了多个触发器,它们会按触发器名称的字母顺序触发。对于 BEFORE 触发器,每个触发器返回的、可能已经被修改过的行都会成为下一个触发器的输入。如果任何
BEFORE 触发器返回 NULL,则会放弃对该行执行该操作,并且后续触发器(针对该行)都不会再触发。
通常,行级 BEFORE 触发器用于检查或修改将要插入或更新的数据。例如,BEFORE 触发器可以用来把当前时间写入某个
timestamp 列,或者检查该行的两个元素是否一致。行级 AFTER 触发器最适合用于把更新传播到其他表,或对其他表执行一致性检查。之所以这样分工,是因为 AFTER 触发器可以确定自己看到的是该行的最终值,而 BEFORE 触发器不能;它之后可能还会有其他 BEFORE 触发器触发。如果没有特定理由把触发器做成
BEFORE 或 AFTER,BEFORE 的情形效率更高,因为有关该操作的信息不必保存到语句结束。
如果触发器函数执行 SQL 命令,那么这些命令可能会再次触发触发器。这就是所谓的级联触发器。对级联层数没有直接限制。级联还有可能导致同一个触发器被递归调用;例如,一个 INSERT 触发器可能执行一条向同一个表再插入一行的命令,从而导致该 INSERT 触发器再次触发。因此,在这种场景下,避免无限递归是触发器编写者自己的责任。
在定义触发器时,可以为它指定参数。在触发器定义中包含参数,是为了让需求相似的不同触发器能够调用同一个函数。举例来说,可以有一个通用触发器函数,它接受两个列名作为参数,把当前用户写入其中一个列,把当前时间戳写入另一个列。只要编写得当,这个触发器函数就应独立于它所作用的具体表。因此,同一个函数可用于任何具有适当列的表上的 INSERT 事件,例如自动跟踪某个事务表中记录的创建。如果把它定义成
UPDATE 触发器,还可以用来跟踪最近更新事件。trigger.
每种支持触发器的编程语言都有自己的方法,使触发器输入数据可供触发器函数使用。这些输入数据包括触发器事件的类型(例如 INSERT 或
UPDATE),以及 CREATE TRIGGER
中列出的任何参数。对于行级触发器,输入数据还包括
INSERT 和 UPDATE 触发器的
NEW 行,以及/或者 UPDATE 和
DELETE 触发器的 OLD 行。语句级触发器目前无法检查该语句修改的各个行。