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 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

39.7. 规则与触发器 #

许多使用触发器可以完成的事情也可以通过 PostgreSQL 规则系统来实现。规则无法实现的一些约束,特别是外键,是规则无法做到的。可以放置一个限定规则,如果某一列的值不存在于另一张表中,则将命令重写为 NOTHING。但那样数据会被静默丢弃,这不是个好主意。如果需要对有效值进行检查,并在值为无效时生成错误消息,则必须通过触发器完成。

在本章中,我们专注于使用规则来更新视图。本章中所有更新规则的示例也可以使用视图上的 INSTEAD OF 触发器来实现。编写这类触发器通常比编写规则更容易,尤其是当需要复杂的逻辑来执行更新时。

对于两者都可以实现的功能,哪种更好取决于数据库的使用方式。触发器对每个受影响的行都会触发一次。规则修改查询或生成额外的查询。因此,如果一个语句影响了许多行,那么一个发出一个额外命令的规则可能比一个为每一行调用并必须多次重新确定做什么的触发器更快。然而,触发器方法在概念上比规则方法简单得多,而且新手更容易正确使用。

这里我们展示了一个关于规则与触发器选择在一种情况下如何发挥作用的例子。有两张表

CREATE TABLE computer (
    hostname        text,    -- indexed
    manufacturer    text     -- indexed
);

CREATE TABLE software (
    software        text,    -- indexed
    hostname        text     -- indexed
);

两张表都有数万行,并且 hostname 上的索引是唯一的。规则或触发器应实现一个约束,删除 software 中引用了已删除计算机的行。触发器将使用此命令

DELETE FROM software WHERE hostname = $1;

由于触发器是为从 computer 中删除的每一行单独调用的,因此它可以准备并保存该命令的计划,并将 hostname 值作为参数传递。规则的写法如下

CREATE RULE computer_del AS ON DELETE TO computer
    DO DELETE FROM software WHERE hostname = OLD.hostname;

现在我们来看不同类型的删除。在以下情况下

DELETE FROM computer WHERE hostname = 'mypc.local.net';

computer 通过索引扫描(速度快),触发器发出的命令也将使用索引扫描(速度也快)。规则产生的额外命令将是

DELETE FROM software WHERE computer.hostname = 'mypc.local.net'
                       AND software.hostname = computer.hostname;

由于设置了适当的索引,规划器将创建一个计划

Nestloop
  ->  Index Scan using comp_hostidx on computer
  ->  Index Scan using soft_hostidx on software

因此,触发器和规则实现之间的速度差异不会太大。

对于下一个删除,我们想删除所有 hostnameold 开头的 2000 台计算机。有两种可能的命令可以做到这一点。一种是

DELETE FROM computer WHERE hostname >= 'old'
                       AND hostname <  'ole'

规则添加的命令将是

DELETE FROM software WHERE computer.hostname >= 'old' AND computer.hostname < 'ole'
                       AND software.hostname = computer.hostname;

计划是

Hash Join
  ->  Seq Scan on software
  ->  Hash
    ->  Index Scan using comp_hostidx on computer

另一种可能的命令是

DELETE FROM computer WHERE hostname ~ '^old';

这导致了规则添加的命令的以下执行计划

Nestloop
  ->  Index Scan using comp_hostidx on computer
  ->  Index Scan using soft_hostidx on software

这表明,规划器没有意识到 computerhostname 的限定条件也可以用于 software 上的索引扫描,当有多个限定条件通过 AND 组合在一起时,这正是它在命令的正则表达式版本中所做的。触发器将针对需要删除的 2000 台旧计算机中的每一台调用一次,这将导致对 computer 的一次索引扫描和对 software 的 2000 次索引扫描。规则实现将通过两个使用索引的命令来完成。而且,这取决于 software 表的总体大小,规则在顺序扫描情况下是否仍然更快。触发器通过 SPI 管理器执行 2000 条命令需要一些时间,即使所有索引块都会很快进入缓存。

我们看的最后一个命令是

DELETE FROM computer WHERE manufacturer = 'bim';

这同样可能导致从 computer 中删除许多行。因此,触发器将再次通过执行器运行许多命令。规则生成的命令将是

DELETE FROM software WHERE computer.manufacturer = 'bim'
                       AND software.hostname = computer.hostname;

该命令的计划将再次是两个索引扫描的嵌套循环,只是使用了 computer 上的一个不同的索引

Nestloop
  ->  Index Scan using comp_manufidx on computer
  ->  Index Scan using soft_hostidx on software

在任何这些情况下,规则系统的额外命令或多或少都独立于命令中受影响的行数。

总结来说,只有当规则的操作导致了大型且糟糕的连接(一种规划器失败的情况)时,规则才会比触发器明显慢。

提交更正

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