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

39.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 触发器的效果,如果引用了 EXCLUDED 列。但是,对于两组行级 BEFORE 触发器执行,不需要有 EXCLUDED 列引用。当存在 BEFORE INSERTBEFORE UPDATE 行级触发器更改正在插入/更新的行时,应考虑出现意外结果的可能性(即使修改或多或少是等效的,如果它们不是幂等的,这也会造成问题)。请注意,无论 UPDATE 是否影响任何行(以及是否曾经采用替代 UPDATE 路径),当指定 ON CONFLICT DO 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 定义单独的触发器。相反,语句级或行级 UPDATEDELETEINSERT 触发器会根据(对于语句级触发器)MERGE 查询中指定的操作以及(对于行级触发器)执行的操作来触发。

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

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

不打算导致上述任一行为的行级 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 触发器。避免在这种情况下出现无限递归是触发器程序员的责任。

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

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

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