许多能够用触发器完成的事情,同样也可以用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 台hostname以old开头的计算机。有两种命令可以做到这件事。其中一种是:
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组合在一起时,规划器并没有意识到,对computer中hostname的限制同样也可以用于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
在上述任何一种情况下,规则系统产生的额外命令都或多或少与该命令影响的行数无关。
总结来说,只有当规则动作导致了规模很大且条件很差的连接,而规划器又无能为力时,规则才会明显慢于触发器。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。