Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

37.1. 触发器行为概述 #

触发器是一种规范,数据库将每当执行某种类型的操作时自动执行特定函数。触发器可以附加到表(分区或非分区)、视图和外部表。

可以在表和外键表上定义触发器,以便在任何 INSERTUPDATEDELETE 操作之前或之后执行,每次修改一行或每个SQL语句。还可以将 UPDATE 触发器设置为仅在 UPDATE 语句的 SET 子句中提到某些列时才触发。触发器也可以在 TRUNCATE 语句中触发。如果触发事件发生,触发器的函数将在适当时间被调用以处理该事件。

可以在视图上定义触发器,以便在 INSERTUPDATEDELETE 操作中或其替代方式中执行。此类 INSTEAD OF 触发器会针对视图中需要修改的每行触发一次。触发器的函数负责对视图的基础基本表执行必要的修改,并在适当的情况下返回修改后的行,因为它将出现在视图中。还可以定义视图上的触发器,以便每个SQL语句在 INSERTUPDATEDELETE 操作之前或之后执行一次。但是,仅当视图上也有 INSTEAD OF 触发器时,才触发此类触发器。否则,针对视图的任何语句都必须重新编写为影响其基础基本表的语句,然后将被触发的触发器将连接至基本表。

必须在创建触发器本身之前定义触发器函数。触发器函数必须声明为一个不带参数的函数,返回类型 trigger。(触发器函数通过特殊传递的 TriggerData 结构接收输入,而不是以普通函数参数的形式接收输入。)

创建合适的触发器函数后,使用 CREATE TRIGGER 建立触发器。同一个触发器函数可用于多个触发器。

PostgreSQL 提供 逐行 触发器和 逐语句 触发器。对于逐行触发器,针对触发触发器的语句影响的每一行,都会调用触发器函数一次。相反,逐语句触发器仅在执行适当语句时调用一次,而不管该语句影响多少行。尤其是,影响 0 行的语句仍会导致执行任何适用的逐语句触发器。这两种类型的触发器有时分别称为 行级 触发器和 语句级 触发器。针对 TRUNCATE 的触发器只能在语句级别定义,不能逐行定义。

触发器还根据其在操作 之前之后代替 触发的方式进行分类。它们分别称为 BEFORE 触发器、AFTER 触发器和 INSTEAD OF 触发器。语句级别的 BEFORE 触发器在语句开始执行任何操作之前自然会触发,而语句级别的 AFTER 触发器则在语句的最后触发。这些类型的触发器可定义在表、视图或外键表中。行级别的 BEFORE 触发器会在对特定行进行操作之前立即触发,而行级别的 AFTER 触发器则在语句结束时触发(但在任何语句级别的 AFTER 触发器之前)。这些类型的触发器只能定义在表和外键表上,不能定义在视图上。INSTEAD OF 触发器只能定义在视图上,并且只能在行级别;它们在视图中的每一行被识别为需要操作时立即触发。

如果 AFTER 触发器被定义为 约束触发器,则其执行可以推迟到事务结束,而不是语句结束。在所有情况下,触发器作为触发它的语句所属事务的一部分执行,因此如果语句或触发器导致错误,则这两个的影响都将被回滚。

如果 INSERT 包含 ON CONFLICT DO UPDATE 子句,则有可能在触发行上执行行级别的 BEFORE INSERT,然后执行 BEFORE UPDATE 触发器。如果触发器不是幂等的,则此类交互可能会很复杂,因为由 BEFORE INSERT 触发器所做的更改将由 BEFORE UPDATE 触发器看到,包括对 EXCLUDED 列的更改。

请注意,在指定了 ON CONFLICT DO UPDATE 时将执行语句级别的 UPDATE 触发器,而不管 UPDATE 是否影响了任何行(并且无论是否走过备用的 UPDATE 路径)。带有 ON CONFLICT DO UPDATE 子句的 INSERT 将首先执行语句级别的 BEFORE INSERT 触发器,然后执行语句级别的 BEFORE UPDATE 触发器,再然后执行语句级别的 AFTER UPDATE 触发器,最后执行语句级别的 AFTER INSERT 触发器。

针对继承或分区层次结构中的父表的语句不会导致触发受影响子表的语句级触发器;只有触发父表的语句级触发器。但是,将触发任何受影响子表的行级触发器。

如果对分区表的 UPDATE 导致一行移至其他分区,这将被视为从原始分区 DELETE,然后 INSERT 到新分区。在这种情况下,将在原始分区触发所有行级 BEFORE UPDATE 触发器以及所有行级 BEFORE DELETE 触发器。然后将在目标分区触发所有行级 BEFORE INSERT 触发器。当所有这些触发器影响正在移动的行时,应考虑出现意外结果的可能性。就 AFTER ROW 触发器而言,将应用 AFTER DELETEAFTER INSERT 触发器;但不会应用 AFTER UPDATE 触发器,因为 UPDATE 已被转换为 DELETEINSERT。就语句级触发器而言,即使发生行移动,也不会触发任何 DELETEINSERT 触发器;只会触发在 UPDATE 语句中用于目标表的 UPDATE 触发器。

未为 MERGE 定义独立的触发器。相反,根据(对于语句级触发器而言)在 MERGE 查询中指定的动作,以及(对于行级触发器而言)执行的动作,触发语句级或行级 UPDATEDELETEINSERT 触发器。

在运行 MERGE 命令时,针对 MERGE 命令操作中指定事件的语句级 BEFOREAFTER 触发器将触发,不管该操作是否最终执行。这与不会更新任何行的 UPDATE 语句相同,但会触发语句级触发器。行级触发器仅在实际更新、插入或删除行时才触发。因此,在针对某些类型操作触发语句级触发器时,不会针对同类型操作触发行级触发器,这完全合法。

由每语句触发器调用的触发器函数应始终返回 NULL。由每行触发器调用的触发器函数可以选择向调用执行器返回表行(HeapTuple 类型的值)。操作前触发的行级触发器有以下选择

不打算引发这两种行为的行级 BEFORE 触发器必须小心将其结果返回为传入的同一行(即,INSERTUPDATE 触发器的 NEW 行,DELETE 触发器的 OLD 行)。

对行级别的 INSTEAD OF 触发器应返回 NULL 以指示它未修改视图底层基础表的任何数据,或其应返回传入的视图行(对于 INSERTUPDATE 操作为 NEW 行,或对于 DELETE 操作为 OLD 行)。非空返回值用于指示触发器执行视图中必要的数据修改。这会导致递增受命令影响的行数计数。对于仅 INSERTUPDATE 操作,触发器可以在返回前修改 NEW 行。这将更改 INSERT RETURNINGUPDATE RETURNING 返回的数据,且在视图不会显示提供的确切相同数据时十分有用。

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

对生成列适用一些注意事项。存储的生成列在 BEFORE 触发器后和 AFTER 触发器前被计算。因此,可检查 AFTER 触发器中的生成值。在 BEFORE 触发器中,OLD 行包含旧的生成值(如预期的那样),但 NEW 行尚未包含新的生成值,且不应对此行进行访问。在 C 语言界面中,该点的列内容未定义;更高级别的编程语言应禁止在 BEFORE 触发器中访问 NEW 行中的存储的生成列。忽略 BEFORE 触发器中生成列的值的更改,并且这些更改将被覆盖。

如果为同一关系的同一事件定义了多个触发器,那么会按触发器名称按字母顺序触发触发器。在 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 触发器再次被触发。在这种情况下,避免无限递归是触发器程序员的责任。

如果外键约束指定了引用行为(即级联更新或删除),则通过对引用表中的普通 SQL 更新或删除命令来执行这些行为。尤其是,对引用表存在的任何触发器都将为这些更改触发。如果此类触发器修改或阻止了这些命令中的一个命令的效果,则最终结果可能是破坏引用完整性。避免这种情况是触发器编写者的责任。

在定义触发器时,可以为其指定参数。在触发器定义中包含参数的目的是为了允许多个具有相似要求的触发器调用同一函数。例如,可以有一个通用的触发器函数,将两个列名称作为其参数,并将当前用户放入一个名称中,将当前时间戳放入另一个名称中。编写正确后,此触发器函数将独立于触发它的特定表。因此,可以将同一函数用于具有合适列的任何表的 INSERT 事件,例如,自动跟踪事务表中的记录创建。如果定义为 UPDATE 触发器,也可用于跟踪最后更新事件。

支持触发器的每种编程语言都有自己的方法使触发器函数可以使用触发器输入数据。此输入数据包括触发器事件的类型(例如 INSERTUPDATE)以及 CREATE TRIGGER 中列出的任何参数。对于行级触发器,输入数据还包括 INSERTUPDATE 触发器的 NEW 行和/或 UPDATEDELETE 触发器的 OLD 行。

默认情况下,语句级触发器没有办法检查语句修改的单个行。但是,AFTER STATEMENT 触发器可以请求创建过渡表,以使受影响的行集合可供触发器使用。AFTER ROW 触发器也可以请求过渡表,以便它们可以看到表中的总变化以及它们当前正在触发的单个行中的变化。检查过渡表的方法再次取决于所使用的编程语言,但典型的方法是使过渡表充当可以通过触发器函数中发出的 SQL 命令访问的只读临时表。