受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

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上的索引都是唯一的。规则或触发器应实现这样一个约束:当删除computer中的某一行时,也删除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

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

在下一个删除场景中,我们想去掉全部 2000 台hostnameold开头的计算机。有两种命令可以做到这件事。其中一种是:

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

这说明,当多个条件表达式通过AND组合在一起时,规划器并没有意识到,对computerhostname的限制同样也可以用于software上的索引扫描,而在该命令的正则表达式版本里它却能做到。触发器会对需要删除的 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

在上述任何一种情况下,规则系统产生的额外命令都或多或少与该命令影响的行数无关。

总结来说,只有当规则动作导致了规模很大且条件很差的连接,而规划器又无能为力时,规则才会明显慢于触发器。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。