选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.1 已于 2016 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

第 36 章 触发器

本章提供有关编写触发器函数的一般信息。触发器函数可以用大多数可用的过程语言编写,包括 PL/pgSQL第 39 章)、 PL/Tcl第 40 章)、 PL/Perl第 41 章)和 PL/Python第 42 章)。读完本章后,你还应查阅所用过程语言对应的章节,了解用该语言编写触发器的语言相关细节。

也可以用 C 编写触发器函数,不过大多数人会觉得使用某种过程语言更容易。目前还不能用普通 SQL 函数语言编写触发器函数。

36.1. 触发器行为概述 #

触发器是一种规定:每当执行某种类型的操作时,数据库就自动执行某个特定函数。触发器可以附加到表和视图上。

在表上,触发器可以定义为在任何 INSERTUPDATEDELETE 操作之前或之后执行;既可以对每个被修改的行执行一次,也可以对每个 SQL 语句执行一次。UPDATE 触发器还可以设置成只有在 UPDATE 语句的 SET 子句中提及某些列时才触发。 Triggers can also fire for TRUNCATE statements. If a trigger event occurs, the trigger's function is called at the appropriate time to handle the event.

在视图上,触发器可以定义为代替 INSERTUPDATEDELETE 操作执行。INSTEAD OF 触发器会对视图中每个需要修改的行触发一次。触发器函数负责对视图底层的基表执行必要的修改,并在适当时返回该修改后的行,使其在视图中呈现为应有的样子。视图上的触发器也可以定义为对每个 SQL 语句执行一次,在 INSERTUPDATEDELETE 操作之前或之后触发。

必须先定义触发器函数,之后才能创建触发器本身。触发器函数必须声明为不接受参数并返回类型 trigger 的函数。(触发器函数通过特殊传入的 TriggerData 结构体接收输入,而不是通过普通函数参数。)

创建出合适的触发器函数后,就可以使用 CREATE TRIGGER 创建触发器。同一个触发器函数可以供多个触发器使用。

PostgreSQL 同时提供 每行 触发器和 每语句 触发器。对于每行触发器,触发它的语句所影响的每一行都会调用一次触发器函数。相反,每语句触发器只会在执行相应语句时调用一次,不管该语句影响了多少行。特别是,即使某个语句一行也没有影响,仍会执行所有适用的每语句触发器。这两类触发器有时也分别称为 行级 触发器和 语句级 触发器。 TRUNCATE 的触发器只能定义为语句级。在视图上,之前或之后触发的触发器只能定义为语句级,而代替 INSERTUPDATEDELETE 触发的触发器只能定义为行级。

触发器还可以按照它们是在操作之前之后,还是取代该操作而触发来分类。它们分别被称为BEFORE 触发器、AFTER 触发器和INSTEAD OF 触发器。 Statement-level BEFORE triggers naturally fire before the statement starts to do anything, while statement-level AFTER triggers fire at the very end of the statement. These types of triggers may be defined on tables or views. Row-level BEFORE triggers fire immediately before a particular row is operated on, while row-level AFTER triggers fire at the end of the statement (but before any statement-level AFTER triggers). These types of triggers may only be defined on tables. Row-level INSTEAD OF triggers may only be defined on views, and fire immediately as each row in the view is identified as needing to be operated on.

由每语句触发器调用的触发器函数应始终返回 NULL。由每行触发器调用的触发器函数则可以根据需要向调用它的执行器返回一个表行(即类型 HeapTuple 的值)。在操作之前触发的行级触发器可以作出如下选择:

  • 它可以返回 NULL,以跳过对当前行的操作。这会指示执行器不要执行引发该触发器的行级操作(即对某个具体表行的插入、修改或删除)。

  • 仅对于行级 INSERTUPDATE 触发器,返回的行会成为将要插入的行,或替换正在更新的行。这使得触发器函数可以修改即将插入或更新的行。

不打算产生上述任何一种行为的行级 BEFORE 触发器,必须小心地返回传入的同一行作为结果(也就是说,INSERTUPDATE 触发器返回 NEW 行, DELETE 触发器返回 OLD 行)。

行级 INSTEAD OF 触发器应当要么返回 NULL,表示它没有修改视图底层基表中的任何数据;要么返回传入的视图行(INSERTUPDATE 操作返回 NEW 行,DELETE 操作返回 OLD 行)。非空返回值用于表明触发器完成了视图中必要的数据修改。这会使该命令所影响行数的计数递增。对于 INSERTUPDATE 操作,触发器可以在返回前修改 NEW 行。这会改变 INSERT RETURNING or UPDATE RETURNING, and is useful when the view will not show exactly the same data that was provided.

对于在操作之后触发的行级触发器,其返回值会被忽略,因此它们可以返回 NULL

如果为同一关系上的同一事件定义了多个触发器,它们会按触发器名称的字母顺序触发。对于 BEFOREINSTEAD OF 触发器,每个触发器返回的、可能已经被修改过的行都会成为下一个触发器的输入。如果任何一个 BEFOREINSTEAD OF 触发器返回 NULL,则会放弃对该行执行该操作,并且后续触发器(针对该行)都不会再触发。

触发器定义还可以指定一个布尔 WHEN 条件,用来测试是否应当触发该触发器。对于行级触发器,WHEN 条件可以检查该行各列的旧值和/或新值。(语句级触发器也可以有 WHEN 条件,不过这个特性对它们用处不大。)在 BEFORE 触发器中, WHEN 条件就在函数即将执行或本会执行之前求值,因此使用 WHEN 与在触发器函数开头测试同样的条件并无实质差别。不过,在 AFTER 触发器中,WHEN 条件会在行更改发生后立即求值,并决定是否要将一个事件排入队列,以便在语句末尾触发该触发器。因此,当 AFTER 触发器的 WHEN 条件不返回真时,就不必排队该事件,也不必在语句末尾重新取出该行。如果触发器只需要针对少数几行触发,这会让修改大量行的语句显著加快。 INSTEAD OF 触发器不支持 WHEN 条件。

通常,行级 BEFORE 触发器用于检查或修改将要插入或更新的数据。例如,BEFORE 触发器可以用来把当前时间写入某个 timestamp 列,或者检查该行的两个元素是否一致。行级 AFTER 触发器最适合用于把更新传播到其他表,或对其他表执行一致性检查。之所以这样分工,是因为 AFTER 触发器可以确定自己看到的是该行的最终值,而 BEFORE 触发器不能;它之后可能还会有其他 BEFORE 触发器触发。如果没有特定理由把触发器做成 BEFOREAFTER,那么 BEFORE 形式更高效,因为关于该操作的信息无需一直保存到语句末尾。

如果触发器函数执行 SQL 命令,那么这些命令可能会再次触发触发器。这就是所谓的级联触发器。对级联层数没有直接限制。级联还有可能导致同一个触发器被递归调用;例如,一个 INSERT 触发器可能执行一条向同一个表再插入一行的命令,从而导致该 INSERT 触发器再次触发。因此,在这种场景下,避免无限递归是触发器编写者自己的责任。

在定义触发器时,可以为它指定参数。在触发器定义中包含参数,是为了让需求相似的不同触发器能够调用同一个函数。举例来说,可以有一个通用触发器函数,它接受两个列名作为参数,把当前用户写入其中一个列,把当前时间戳写入另一个列。只要编写得当,这个触发器函数就应独立于它所作用的具体表。因此,同一个函数可用于任何具有适当列的表上的 INSERT 事件,例如自动跟踪某个事务表中记录的创建。如果把它定义成 UPDATE 触发器,还可以用来跟踪最近更新事件。

每种支持触发器的编程语言都有自己的方法,使触发器输入数据可供触发器函数使用。这些输入数据包括触发器事件的类型(例如 INSERTUPDATE),以及 CREATE TRIGGER 中列出的任何参数。对于行级触发器,输入数据还包括 INSERTUPDATE 触发器的 NEW 行,以及/或者 UPDATEDELETE 触发器的 OLD 行。语句级触发器目前无法检查该语句修改的各个行。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。