支持的版本:当前 (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 命令时,语句级别的 BEFOREAFTER 触发器会为 MERGE 命令操作中指定的事件触发,无论该操作最终是否执行。这与 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 命令访问这些表。

提交更正

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