39.1. 触发器行为概述

触发器是一种规范,规定只要执行某种类型的操作,数据库就应自动执行特定功能。触发器可以附加到表(已分区或未分区),视图和外部表上。

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

在视图上,可以定义触发器来代替INSERTUPDATEDELETE操作执行。对于需要在视图中修改的每一行,都会触发一次此类INSTEAD OF触发器。触发器的功能是对视图的基础基表执行必要的修改,并在适当时返回修改后的行,该行将出现在视图中,这是责任。还可以将视图触发器定义为对每个 SQL 语句执行一次,在INSERTUPDATEDELETE操作之前或之后执行。但是,仅当视图上也有INSTEAD OF触发器时才触发此类触发器。否则,任何针对该视图的语句都必须重写为一个会影响其基础基表的语句,然后将触发的触发器是附加到该基表的触发器。

必须先定义触发器功能,然后才能创建触发器本身。必须将触发函数声明为不带参数且返回类型trigger的函数。 (触发器函数通过特殊传递的TriggerData结构而不是普通函数参数的形式来接收其 Importing.)

一旦创建了合适的触发功能,便使用CREATE TRIGGER构建触发。相同的触发功能可用于多个触发。

PostgreSQL 提供了每行触发器和每语句触发器。对于每行触发器,对于触发该触发器的语句影响的每一行,将触发一次触发器函数。相反,每条语句触发器在执行适当的语句时仅被调用一次,而不管该语句影响的行数如何。特别是,影响零行的语句仍将导致执行任何适用的按语句触发。这两种类型的触发器有时分别称为“行级”触发器和“语句级”触发器。 TRUNCATE上的触发器只能在语句级别定义,不能按行定义。

触发器还根据它们是在操作之前*,之后还是而不是触发。这些分别称为BEFORE触发器,AFTER触发器和INSTEAD OF触发器。语句级BEFORE在语句开始执行任何操作之前自然触发,而语句级AFTER在语句末尾触发。这些类型的触发器可以在表,视图或外部表上定义。行级BEFORE在操作特定行之前立即触发,而行级AFTER在语句结束时(但在任何语句级AFTER触发之前)触发触发。这些类型的触发器只能在表和外部表上定义,而不能在视图上定义; BEFORE行级触发器可能未在分区表上定义。 INSTEAD OF触发器只能在视图上定义,并且只能在行级别上定义;当视图中的每一行都需要操作时,它们会立即触发。

以继承或分区层次结构中的父表为目标的语句不会导致受影响子表的语句级触发器被触发;仅触发父表的语句级触发器。但是,将触发任何受影响的子表的行级触发器。

如果INSERT包含ON CONFLICT DO UPDATE子句,则行级别BEFORE INSERT触发器和行级别BEFORE UPDATE触发器的影响可能都可以通过从更新后的行的最终状态显而易见的方式应用,如果EXCLUDED列被引用。不过,两组要执行的行级BEFORE触发器都不需要EXCLUDED列引用。当同时存在BEFORE INSERTBEFORE UPDATE的行级触发器均会更改要插入/更新的行时,应考虑产生出乎意料的结果的可能性(即使修改或多或少是等效的,这也可能是有问题的,即使它们不相同幂等)。请注意,指定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触发器。

每个语句触发器调用的触发器函数应始终返回NULL。如果选择,逐行触发器调用的触发器函数可以将表行(类型HeapTuple的值)返回给执行者。在操作之前触发的行级触发器具有以下选择:

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

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

不打算导致上述任何一种行为的行级BEFORE触发器必须谨慎返回其结果,即返回传入的同一行(即INSERTUPDATE触发器的NEW行,DELETE触发器的OLD行)。

行级INSTEAD OF触发器应返回NULL,以表明它没有修改视图基础表的任何数据,或者应返回传入的视图行(INSERTUPDATE操作的NEW行,或OLD DELETE个操作的行)。非空返回值用于表示触发器在视图中执行了必要的数据修改。这将导致该命令影响的行数计数增加。仅对于INSERTUPDATE操作,触发器可以在返回NEW行之前对其进行修改。这将更改INSERT RETURNINGUPDATE RETURNING返回的数据,并且在视图将不完全显示所提供的相同数据时很有用。

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

如果为同一关系上的同一事件定义了多个触发器,则将按触发器名称的字母 Sequences 触发这些触发器。对于BEFOREINSTEAD OF触发器,每个触发器返回的可能已修改的行将成为下一个触发器的 Importing。如果任何BEFOREINSTEAD OF触发器返回NULL,则该行的操作将被放弃,并且不会触发该行的后续触发器。

触发器定义还可以指定布尔值WHEN,将对其进行测试以查看是否应触发触发器。在行级触发器中,WHEN条件可以检查该行的列的旧值和/或新值。 (语句级触发器也可以具有WHEN条件,尽管该功能对其并不那么有用.)在BEFORE触发器中,WHEN条件是在函数即将执行或将要执行之前进行评估的,因此使用WHEN与使用WHEN并没有实质性区别在触发功能开始时测试相同的条件。但是,在AFTER触发器中,WHEN条件是在行更新发生后立即评估的,它确定是否有事件在语句结尾处排队触发触发器。因此,当AFTER触发器的WHEN条件未返回 true 时,就不必将事件排队,也不必在语句末尾重新获取行。如果只需要为少数几行触发触发器,则可以大大提高修改多行的语句的速度。 INSTEAD OF触发器不支持WHEN条件。

通常,行级BEFORE触发器用于检查或修改将要插入或更新的数据。例如,可以使用BEFORE触发器将当前时间插入timestamp列,或检查该行的两个元素是否一致。行级AFTER触发器最明智地用于将更新传播到其他表,或针对其他表进行一致性检查。这种分工的原因是,可以确定AFTER触发器可以看到行的最终值,而BEFORE触发器不能确定。之后可能还有其他BEFORE触发器触发。如果没有特定的原因触发BEFOREAFTER,则BEFORE的情况会更有效,因为有关该操作的信息不必保存到语句结束。

如果触发器函数执行 SQL 命令,则这些命令可能会再次触发触发器。这称为级联触发器。级联级别的数量没有直接限制。级联有可能引起同一触发器的递归调用。例如,INSERT触发器可能会执行将同一行插入另一行的命令,从而再次触发INSERT触发器。在这种情况下,避免无限递归是触发器程序员的责任。

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

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

默认情况下,语句级触发器没有任何方法来检查由语句修改的单个行。但是AFTER STATEMENT触发器可以请求创建“转换表”,以使受影响的行集可用于触发器。 AFTER ROW触发器还可以请求转换表,以便他们可以查看表中的全部更改以及当前为其触发的单个行中的更改。再次检查过渡表的方法取决于所使用的编程语言,但是典型的方法是使过渡表像只读临时表一样工作,可以通过触发器函数中发出的 SQL 命令对其进行访问。