2024 年 9 月 26 日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本: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 触发器将针对需要在视图中修改的每一行触发一次。触发器函数负责对视图的底层基本表执行必要的修改,并在适当的情况下返回修改后的行,该行将显示在视图中。视图上的触发器也可以定义为每SQL语句一次,在 INSERTUPDATEDELETE 操作之前或之后执行。但是,此类触发器仅在视图上也有 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 列的更改。

请注意,即使 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 定义单独的触发器。相反,将根据 (针对语句级触发器) 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 命令访问。

提交更正

如果您在文档中看到任何不正确的内容,不符合您对特定功能的体验,或者需要进一步澄清,请使用 此表格 报告文档问题。