↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 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
历史版本PostgreSQL 7.1 已于 2006 年 4 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

17.5. 规则与触发器 #

许多能够用触发器完成的事情,同样也可以用 Postgres 规则系统实现。目前不能用规则实现的内容是一些种类的约束。你可以放置一条带条件的规则:当某列的值没有出现在另一张表中时,把查询重写成 NOTHING。但那样做会悄无声息地丢弃数据,这并不是好主意。如果需要检查值的有效性,并在值无效时生成错误消息,目前就必须使用触发器。

另一方面,在视图上因 INSERT 而触发的触发器可以做到与规则相同的事情:把数据放到别处,并抑制向视图本身的插入。但在 UPDATE 或 DELETE 上它就无能为力了,因为视图关系中没有被扫描的真实数据,触发器因而永远不会被调用。这时只有规则才能解决问题。

对于两者都能实现的事情,哪一种更好取决于数据库的使用方式。触发器会对每个受影响的行触发一次,而规则会修改解析树或生成额外的解析树。因此,如果一条语句会影响很多行,那么发出一条额外查询的规则往往会比触发器更快,因为触发器必须为每一行调用一次,并反复执行其操作。

例如:有两个表:

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

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

两个表都有数千行,并且 hostname 上的索引是唯一索引。hostname 列包含计算机的完整域名。规则或触发器应实现这样一个约束:从 software 中删除引用了被删除主机的行。由于从 computer 中删除的每一行都会调用触发器,它可以在一个准备并保存好的计划中使用语句:

DELETE FROM software WHERE hostname = $1;

并通过参数传入 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 台 hostname 以 'old' 开头的计算机。有两种查询可以做到这件事。其中一种是:

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

规则查询的计划将是:

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 组合在一起时,规划器并没有意识到,对 computer 上 hostname 的限制同样也可以用于 software 上的索引扫描,而在该查询的正则表达式版本里它却能做到。触发器会对需要删除的 2000 台旧计算机中的每一台各调用一次,这意味着对 computer 做一次索引扫描,并对 software 做 2000 次索引扫描。规则实现则用两条走索引的查询完成这件事。不过,在顺序扫描这种情况下,规则是否仍然更快,还取决于 software 表的总体大小。即使所有用于查找的索引块很快都会进入缓存,通过 SPI 管理器执行 2000 条查询仍然要耗费一些时间。

我们要看的最后一个查询是:

DELETE FROM computer WHERE manufacurer = 'bim';

同样,这也可能导致从 computer 中删除很多行。因此触发器又会向执行器触发很多条查询。但规则的计划又将是在两个索引扫描上的嵌套循环,只不过使用了 computer 上的另一个索引:

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

它来自规则的查询:

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

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

另一种情况是 UPDATE 中是否执行动作取决于某个属性是否发生变化。在 Postgres 6.4 版中,规则事件的属性说明被禁用了(它最迟会在 6.5 恢复,也许更早——敬请期待)。因此,目前创建像 shoelace_log 例子中那样规则的唯一方式是使用规则条件。这会导致一条额外的查询总是被执行,即使所关注的属性根本不可能变化,因为它没有出现在初始查询的目标列表中。等这一功能重新启用后,它将成为规则相对触发器的又一个优势。在这种情况下,触发器的优化按定义必然失败,因为"其动作只在某个特定属性被更新时执行"这一事实隐藏在它的功能之中。触发器的定义只允许在行级指定,因此每当某一行被触及,触发器就必须被调用来做决定。而规则系统会通过查看目标列表知道这一点,并在属性未被涉及时完全抑制那条额外的查询。所以,无论带不带条件,规则只会在可能有事可做时才执行其扫描。

只有当规则动作导致了规模很大且条件很差的连接(规划器无能为力的情形)时,规则才会明显慢于触发器。规则是一把大锤。不加小心地使用大锤可能造成巨大的破坏。但用对了手法,它们能把任何钉子一击入帽。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。