支持的版本: 当前 (17) / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2

37.1. 触发器行为概述 #

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

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

在视图上,可以将触发器定义为代替 INSERTUPDATEDELETE 操作执行。这种 INSTEAD OF 触发器对于需要在视图中修改的每一行触发一次。触发器的函数负责对视图的底层基表执行必要的修改,并在适当的情况下返回修改后的行,使其显示在视图中。视图上的触发器也可以定义为在 INSERTUPDATEDELETE 操作之前或之后,每个SQL语句执行一次。但是,只有在视图上也有 INSTEAD OF 触发器时,才会触发此类触发器。否则,任何针对视图的语句都必须重写为影响其底层基表的语句,然后将触发的触发器是附加到基表的触发器。

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

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

PostgreSQL 提供行级触发器和语句级触发器。使用行级触发器时,对于受触发语句影响的每一行,触发器函数都会被调用一次。相比之下,语句级触发器仅在执行适当的语句时调用一次,而不管该语句影响的行数。特别是,影响零行的语句仍会导致执行任何适用的语句级触发器。这两种类型的触发器有时分别称为行级触发器和语句级触发器。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 的值)。在操作之前触发的行级触发器有以下选择:

  • 它可以返回 NULL 以跳过当前行的操作。这指示执行器不执行调用触发器的行级操作(特定表行的插入、修改或删除)。

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

一个行级 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 条件不返回 true 时,无需将事件排队或在语句结束时重新获取该行。如果触发器只需要为少数行触发,那么这可以在修改许多行的语句中显着提高速度。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 命令访问。

提交更正

如果您在文档中发现任何不正确、与您使用特定功能的体验不符或需要进一步澄清的地方,请使用此表格报告文档问题。