2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 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触发器每次需要修改视图中的一行时被触发。触发器函数负责执行对视图底层基表(s)的必要修改,并在适当的情况下返回修改后的行在视图中的样子。视图上的触发器也可以定义为每次为SQL语句执行,在INSERTUPDATEDELETE操作之前或之后执行。但是,这种触发器仅在视图上存在INSTEAD OF触发器时才触发。否则,任何针对视图的语句都必须重写为影响其底层基表(s)的语句,然后触发的是附加到基表(s)上的触发器。

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

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

PostgreSQL同时提供逐行触发器和按语句触发器。对于逐行触发器,触发器函数在触发语句影响的每一行上调用一次。相比之下,按语句触发器仅在执行适用的语句时调用一次,而不管该语句影响的行数。特别是,一个不影响任何行的语句仍然会导致任何适用的按语句触发器被执行。这两种类型的触发器有时分别被称为行级触发器和语句级触发器。对TRUNCATE的触发器只能定义在语句级别,不能逐行定义。

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

如果AFTER触发器被定义为约束触发器,它的执行可以推迟到事务结束,而不是语句结束。在所有情况下,触发器都作为触发它的语句的同一事务的一部分执行,因此如果语句或触发器导致错误,两者的效果都将被回滚。此外,触发器将始终以触发事件的角色运行,除非触发器函数被标记为SECURITY DEFINER,在这种情况下,它将以函数所有者的角色运行。

如果INSERT包含ON CONFLICT DO UPDATE子句,则有可能在触发的行上执行行级BEFORE INSERT,然后是BEFORE UPDATE触发器。如果触发器不是幂等的,这些交互可能会很复杂,因为BEFORE INSERT触发器所做的更改将被BEFORE UPDATE触发器看到,包括对EXCLUDED列的更改。

请注意,即使没有影响任何行(并且无论是否采取了替代的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命令时,语句级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触发器中对生成列值的更改将被忽略并被覆盖。虚拟生成的列在触发器触发时从不计算。在C语言接口中,它们的内容在触发器函数中是未定义的。更高级别的编程语言应防止在触发器中访问虚拟生成的列。

如果对同一关系上的同一事件定义了多个触发器,则触发器将按触发器名称的字母顺序触发。对于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触发器在其之后触发。如果你没有特别的原因将触发器设置为BEFOREAFTERBEFORE情况效率更高,因为操作信息不必保存到语句结束。

如果触发器函数执行SQL命令,这些命令可能会再次触发触发器。这被称为级联触发器。级联级别没有直接限制。级联可能导致同一个触发器递归调用;例如,一个INSERT触发器可能会执行一个命令,将一个额外的行插入到同一个表中,从而再次触发INSERT触发器。避免这种情况下的无限递归是触发器程序员的责任。

如果外键约束指定了引用操作(即,级联更新或删除),这些操作是通过在引用表上执行常规的SQL UPDATEDELETE命令来完成的。特别是,引用表上存在的任何触发器都会为这些更改而被触发。如果这样的触发器修改或阻止了这些命令的效果,最终结果可能是破坏引用完整性。避免这种情况是触发器程序员的责任。

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

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

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

提交更正

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