13.7. 规则与触发器之比较

许多用触发器可以干的事情同样也可以用 PostgreSQL 规则系统来完成. 目前不能用规则来实现的东西是某些约束. 我们还可能在某字段的值没有在另一个表里出现的情况下 用一条合格的规则把查询重写为 NOTHING. 不过这样做数据就会被不声不响的被仍掉,因而这也不是一个好主意. 如果需要检查有效的值,而且如果是无效值出现时要生成一个错误信息, 这种情况下现在我们要用触发器来做.

另一方面,一个用于 INSERT 一个视图的触发器可以做到与规则一样, 把数据放到另外的地方去而取代对视图的插入. 但它不能在 UPDATE 或 DELETE 时做同样的事情, 因为在视图关系里没有可供扫描的真实数据因而触发器将永远不被调用. 这时只有规则可用.

对于两者都可用的情况,哪个更好取决于对数据库的使用. 触发器用于任何涉及到一次的行.规则修改分析树或生成另外一个. 所以如果在一个语句中涉及到多行,一个生成一个额外查询的规则通常可能会比 一个对每一行都分别执行一次的触发器要好一些.

例如:这里有两个表

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

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

两个表都有好几千行,并且hostname是唯一的. hostname字段包含计算机完整的域名. 规则/触发器应该对来自软件的删除已删除主机的行的动作进行约束. 因为触发器在每个独立的行删除的时候都要调用,它可以使用下面语句

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 个以 '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

这表明,优化器不能认识到表 computer 的hostname (计算机主机名) 的资格(条件)在多个资格表达式以 AND (与)的方式组合在一起时 同样可以用于 software (软件),就象在用规则表达式的查询里一样. 触发器将在任何 2000 个要被删除的旧计算机里被调用一次, 结果是对 computer 的一次索引扫描和对 software 的2000次索引扫描. 规则的实现将在两个对索引的查询实现之. 所以这是由 software 表的实际大小决定规则进行了顺序扫描后是否还是快一些. 2000 个在 SPI管理器上的查询的执行是要点时间的, 即使所有要使用的索引块都很快在缓冲里出现.

我们看看最后一个查询

DELETE FROM computer WHERE manufacurer = 'bim';

同样,这也会导致从 computer 表里的多行删除. 所以触发器同样会向执行器提交很多查询. 但规则规划又将是对两个IndexScan (索引扫描)的 Nestloop (内部循环). 只对 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, 这时某字段的更改决定一个规则动作(action)是否被执行. 在 PostgreSQL 版本 6.4 里, 对规则事件的字段/属性声明被取消了 (将在 6.5 晚些版本,也可能早些的版本中恢复 -耐心点). 所以现在创建象在 showlace_log 里那样的规则的唯一的办法是用一个规则资格(条件)来做. 结果是引入了一个永远执行的额外的查询, 即使我们关心的字段/属性因为没有在初始的查询的目标列表里出现 而不能修改也是这样. 当这个特性(字段属性声明)重新可用后, 这将是规则优于触发器的又一个方面. 在这种情况下的触发器的定义必然会无法优化, 因为触发器的动作只有在声明的字段的更新 被隐含在触发器的功能里面时才能执行. 对触发器的执行只允许到行的级别,所以当涉及到行时, 触发器就会按定义被触发进行动作. 而规则系统将通过对目标列表的扫描获知(是否动作) 并且在字段/属性没有被涉及到时把多余的查询完全去掉. 所以不管规则合格与否,如果有可能有些事情要做的话,都将进行扫描.

规则只是在它们的动作(action) 生成了又大又烂的资格(条件)连接时才比触发器有较大速度差异, 这时优化器将失效.规则是大榔头.大榔头的不慎使用会导致大破坏. 但合理的用劲,它们可以钉上任何钉子.