PostgreSQL 中的视图是使用规则系统实现的。视图基本上是一个空表(没有实际存储),具有 ON SELECT DO INSTEAD
规则。按照惯例,该规则被命名为 _RETURN
。因此,类似如下的视图
CREATE VIEW myview AS SELECT * FROM mytab;
几乎与如下内容相同
CREATE TABLE myview (same column list as mytab
);
CREATE RULE "_RETURN" AS ON SELECT TO myview DO INSTEAD
SELECT * FROM mytab;
尽管您实际上不能这样写,因为不允许表具有 ON SELECT
规则。
视图还可以具有其他类型的 DO INSTEAD
规则,允许在视图上执行 INSERT
、UPDATE
或 DELETE
命令,尽管它缺乏底层存储。这将在下面的第 39.2.4 节中进一步讨论。
SELECT
规则如何工作 #ON SELECT
的规则作为最后一步应用于所有查询,即使给定的命令是 INSERT
、UPDATE
或 DELETE
。它们与其他命令类型的规则具有不同的语义,因为它们会就地修改查询树,而不是创建一个新的查询树。因此,首先描述 SELECT
规则。
目前,ON SELECT
规则中只能有一个操作,它必须是一个无条件的 SELECT
操作,该操作是 INSTEAD
。此限制是使规则足够安全,可以向普通用户开放,并且它将 ON SELECT
规则限制为像视图一样运行。
本章的示例是两个连接视图,它们执行一些计算,以及另外一些依次使用它们的视图。稍后,通过为 INSERT
、UPDATE
和 DELETE
操作添加规则来自定义这两个初始视图之一,以便最终结果是一个像真实表一样具有某些神奇功能的视图。这不是一个简单的入门示例,这使得事情更难理解。但是,最好有一个逐步涵盖所有讨论点的示例,而不是许多不同的示例,这些示例可能会在脑海中混淆。
在最开始的两个规则系统描述中,我们需要的真实表如下
CREATE TABLE shoe_data ( shoename text, -- primary key sh_avail integer, -- available number of pairs slcolor text, -- preferred shoelace color slminlen real, -- minimum shoelace length slmaxlen real, -- maximum shoelace length slunit text -- length unit ); CREATE TABLE shoelace_data ( sl_name text, -- primary key sl_avail integer, -- available number of pairs sl_color text, -- shoelace color sl_len real, -- shoelace length sl_unit text -- length unit ); CREATE TABLE unit ( un_name text, -- primary key un_fact real -- factor to transform to cm );
如您所见,它们代表鞋店数据。
视图创建如下
CREATE VIEW shoe AS SELECT sh.shoename, sh.sh_avail, sh.slcolor, sh.slminlen, sh.slminlen * un.un_fact AS slminlen_cm, sh.slmaxlen, sh.slmaxlen * un.un_fact AS slmaxlen_cm, sh.slunit FROM shoe_data sh, unit un WHERE sh.slunit = un.un_name; CREATE VIEW shoelace AS SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace_data s, unit u WHERE s.sl_unit = u.un_name; CREATE VIEW shoe_ready AS SELECT rsh.shoename, rsh.sh_avail, rsl.sl_name, rsl.sl_avail, least(rsh.sh_avail, rsl.sl_avail) AS total_avail FROM shoe rsh, shoelace rsl WHERE rsl.sl_color = rsh.slcolor AND rsl.sl_len_cm >= rsh.slminlen_cm AND rsl.sl_len_cm <= rsh.slmaxlen_cm;
用于 shoelace
视图的 CREATE VIEW
命令(这是我们拥有的最简单的视图)将创建一个关系 shoelace
以及 pg_rewrite
中的一个条目,该条目指示当在查询的范围表中引用关系 shoelace
时必须应用重写规则。该规则没有规则限定(稍后,将使用非 SELECT
规则讨论,因为 SELECT
规则当前不能具有规则限定),并且它是 INSTEAD
。请注意,规则限定与查询限定不同。我们的规则的操作具有查询限定。该规则的操作是一个查询树,它是视图创建命令中 SELECT
语句的副本。
您可以在 pg_rewrite
条目中看到的 NEW
和 OLD
的两个额外范围表条目对于 SELECT
规则不感兴趣。
现在,我们填充 unit
、shoe_data
和 shoelace_data
,并在视图上运行一个简单的查询
INSERT INTO unit VALUES ('cm', 1.0); INSERT INTO unit VALUES ('m', 100.0); INSERT INTO unit VALUES ('inch', 2.54); INSERT INTO shoe_data VALUES ('sh1', 2, 'black', 70.0, 90.0, 'cm'); INSERT INTO shoe_data VALUES ('sh2', 0, 'black', 30.0, 40.0, 'inch'); INSERT INTO shoe_data VALUES ('sh3', 4, 'brown', 50.0, 65.0, 'cm'); INSERT INTO shoe_data VALUES ('sh4', 3, 'brown', 40.0, 50.0, 'inch'); INSERT INTO shoelace_data VALUES ('sl1', 5, 'black', 80.0, 'cm'); INSERT INTO shoelace_data VALUES ('sl2', 6, 'black', 100.0, 'cm'); INSERT INTO shoelace_data VALUES ('sl3', 0, 'black', 35.0 , 'inch'); INSERT INTO shoelace_data VALUES ('sl4', 8, 'black', 40.0 , 'inch'); INSERT INTO shoelace_data VALUES ('sl5', 4, 'brown', 1.0 , 'm'); INSERT INTO shoelace_data VALUES ('sl6', 0, 'brown', 0.9 , 'm'); INSERT INTO shoelace_data VALUES ('sl7', 7, 'brown', 60 , 'cm'); INSERT INTO shoelace_data VALUES ('sl8', 1, 'brown', 40 , 'inch'); SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm -----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 7 | brown | 60 | cm | 60 sl3 | 0 | black | 35 | inch | 88.9 sl4 | 8 | black | 40 | inch | 101.6 sl8 | 1 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 0 | brown | 0.9 | m | 90 (8 rows)
这是您可以在我们的视图上执行的最简单的 SELECT
,因此我们借此机会解释视图规则的基础知识。SELECT * FROM shoelace
由解析器解释并生成查询树
SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace shoelace;
并将其提供给规则系统。规则系统遍历范围表,并检查是否有任何关系的规则。在处理 shoelace
(到目前为止唯一的一个)的范围表条目时,它会找到具有查询树的 _RETURN
规则
SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace old, shoelace new, shoelace_data s, unit u WHERE s.sl_unit = u.un_name;
为了展开视图,重写器只是创建一个包含规则的操作查询树的子查询范围表条目,并将此范围表条目替换为引用视图的原始条目。生成的重写查询树几乎与您键入的
SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM (SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace_data s, unit u WHERE s.sl_unit = u.un_name) shoelace;
相同。但是,有一个区别:子查询的范围表有两个额外的条目 shoelace old
和 shoelace new
。这些条目不直接参与查询,因为子查询的连接树或目标列表没有引用它们。重写器使用它们来存储最初存在于引用视图的范围表条目中的访问权限检查信息。这样,执行器仍然会检查用户是否具有访问视图的适当权限,即使在重写的查询中没有直接使用该视图。
那是应用的第一个规则。规则系统将继续检查顶部查询中剩余的范围表条目(在此示例中,没有更多),它将递归检查添加的子查询中的范围表条目,以查看它们中是否有任何引用视图。(但它不会展开 old
或 new
- 否则我们将有无限递归!)在此示例中,shoelace_data
或 unit
没有重写规则,因此重写完成,上面是提供给计划器的最终结果。
现在,我们想要编写一个查询,找出目前在商店中哪些鞋子有匹配的鞋带(颜色和长度),并且完全匹配的对的总数大于或等于 2。
SELECT * FROM shoe_ready WHERE total_avail >= 2; shoename | sh_avail | sl_name | sl_avail | total_avail ----------+----------+---------+----------+------------- sh1 | 2 | sl1 | 5 | 2 sh3 | 4 | sl7 | 7 | 4 (2 rows)
这次解析器的输出是查询树
SELECT shoe_ready.shoename, shoe_ready.sh_avail, shoe_ready.sl_name, shoe_ready.sl_avail, shoe_ready.total_avail FROM shoe_ready shoe_ready WHERE shoe_ready.total_avail >= 2;
应用的第一个规则将是 shoe_ready
视图的规则,它将生成查询树
SELECT shoe_ready.shoename, shoe_ready.sh_avail, shoe_ready.sl_name, shoe_ready.sl_avail, shoe_ready.total_avail FROM (SELECT rsh.shoename, rsh.sh_avail, rsl.sl_name, rsl.sl_avail, least(rsh.sh_avail, rsl.sl_avail) AS total_avail FROM shoe rsh, shoelace rsl WHERE rsl.sl_color = rsh.slcolor AND rsl.sl_len_cm >= rsh.slminlen_cm AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready WHERE shoe_ready.total_avail >= 2;
类似地,将 shoe
和 shoelace
的规则替换为子查询的范围表,从而生成一个三级最终查询树
SELECT shoe_ready.shoename, shoe_ready.sh_avail, shoe_ready.sl_name, shoe_ready.sl_avail, shoe_ready.total_avail FROM (SELECT rsh.shoename, rsh.sh_avail, rsl.sl_name, rsl.sl_avail, least(rsh.sh_avail, rsl.sl_avail) AS total_avail FROM (SELECT sh.shoename, sh.sh_avail, sh.slcolor, sh.slminlen, sh.slminlen * un.un_fact AS slminlen_cm, sh.slmaxlen, sh.slmaxlen * un.un_fact AS slmaxlen_cm, sh.slunit FROM shoe_data sh, unit un WHERE sh.slunit = un.un_name) rsh, (SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace_data s, unit u WHERE s.sl_unit = u.un_name) rsl WHERE rsl.sl_color = rsh.slcolor AND rsl.sl_len_cm >= rsh.slminlen_cm AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready WHERE shoe_ready.total_avail > 2;
这看起来可能效率不高,但是计划器将通过“拉起”子查询将其折叠为单级查询树,然后它将像我们手动写出它们一样计划连接。因此,折叠查询树是重写系统不必关心的优化。
SELECT
语句中的视图规则 #在上面的视图规则描述中,查询树的两个细节没有被触及。这些是命令类型和结果关系。实际上,视图规则不需要命令类型,但是如果结果关系是视图,则结果关系可能会影响查询重写器的工作方式,因为需要特别小心。
SELECT
的查询树与任何其他命令的查询树之间只有几个差异。显然,它们具有不同的命令类型,并且对于 SELECT
以外的命令,结果关系指向结果应转到的范围表条目。其他一切都是绝对相同的。因此,有两个表 t1
和 t2
,具有列 a
和 b
,这两个语句的查询树
SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a; UPDATE t1 SET b = t2.b FROM t2 WHERE t1.a = t2.a;
几乎相同。特别是
范围表包含表 t1
和 t2
的条目。
目标列表包含一个变量,该变量指向表 t2
的范围表条目的列 b
。
限定表达式比较两个范围表条目的列 a
是否相等。
连接树显示 t1
和 t2
之间的简单连接。
结果是,两个查询树会产生相似的执行计划:它们都是对两个表的连接。对于 UPDATE
,规划器会将 t1
中缺失的列添加到目标列表中,最终的查询树将读取为
UPDATE t1 SET a = t1.a, b = t2.b FROM t2 WHERE t1.a = t2.a;
因此,在连接上运行的执行器将产生与以下完全相同的结果集
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
但是在 UPDATE
中有一个小问题:执行计划中执行连接的部分并不关心连接的结果是用于什么。它只是生成一个行结果集。一个是 SELECT
命令,另一个是 UPDATE
命令这一事实是在执行器中更高层处理的,它知道这是一个 UPDATE
,并且知道此结果应该进入表 t1
。但是,其中哪些行必须被新行替换?
为了解决这个问题,另一个条目被添加到 UPDATE
(以及 DELETE
)语句的目标列表中:当前的元组 ID (CTID)。 这是一个系统列,其中包含该行的文件块号和在块中的位置。知道表之后,CTID可以用来检索要更新的 t1
的原始行。在将CTID添加到目标列表后,查询实际上看起来像
SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
现在,PostgreSQL 的另一个细节出现了。旧的表行不会被覆盖,这就是为什么 ROLLBACK
很快的原因。在 UPDATE
中,新的结果行被插入到表中(在剥离了CTID之后),并且在旧行的行头中,即CTID指向的行头中,cmax
和 xmax
条目被设置为当前的命令计数器和当前事务 ID。因此,旧行被隐藏,并且在事务提交后,垃圾回收器最终可以删除死行。
了解所有这些之后,我们可以简单地以完全相同的方式将视图规则应用于任何命令。没有任何区别。
以上演示了规则系统如何将视图定义合并到原始查询树中。在第二个示例中,从一个视图进行的简单 SELECT
创建了一个最终的查询树,该查询树是 4 个表的连接(unit
使用了两次,名称不同)。
使用规则系统实现视图的好处在于,规划器拥有所有关于哪些表必须被扫描的信息,以及这些表之间的关系,加上视图的限制性限定,以及原始查询的限定,所有这些都在一个单一的查询树中。当原始查询已经是视图上的连接时,情况仍然如此。规划器必须决定执行查询的最佳路径,并且规划器拥有的信息越多,这个决定就越好。并且在 PostgreSQL 中实现的规则系统确保了所有关于查询的信息都可以在那一点获得。
如果将视图命名为 INSERT
、UPDATE
、DELETE
或 MERGE
的目标关系会发生什么?执行上述替换将给出一个查询树,其中结果关系指向子查询范围表条目,这将不起作用。但是,PostgreSQL 可以通过几种方式来支持更新视图的外观。按照用户体验的复杂程度,它们是:自动将基础表替换为视图,执行用户定义的触发器,或按照用户定义的规则重写查询。这些选项将在下面讨论。
如果子查询从单个基础关系中选择并且足够简单,则重写器可以自动将子查询替换为基础关系,以便以适当的方式将 INSERT
、UPDATE
、DELETE
或 MERGE
应用于基础关系。为此目的而足够“简单”的视图称为自动可更新。有关可以自动更新的视图类型的详细信息,请参阅 CREATE VIEW。
或者,该操作可以由视图上的用户提供的 INSTEAD OF
触发器来处理(请参阅 CREATE TRIGGER)。在这种情况下,重写的工作方式略有不同。对于 INSERT
,重写器根本不处理视图,将其保留为查询的结果关系。对于 UPDATE
、DELETE
和 MERGE
,仍然需要展开视图查询以生成该命令将尝试更新、删除或合并的“旧”行。因此,视图会像往常一样展开,但另一个未展开的范围表条目会添加到查询中,以表示视图作为结果关系的能力。
现在出现的问题是如何识别视图中要更新的行。回想一下,当结果关系是一个表时,会将一个特殊的CTID条目添加到目标列表中,以标识要更新的行的物理位置。如果结果关系是一个视图,这将不起作用,因为视图没有任何CTID,因为它的行没有实际的物理位置。相反,对于 UPDATE
、DELETE
或 MERGE
操作,一个特殊的 wholerow
条目会添加到目标列表中,它会展开以包含视图中的所有列。执行器使用此值向 INSTEAD OF
触发器提供“旧”行。触发器有责任根据旧行和新行值来确定要更新的内容。
另一种可能性是用户定义 INSTEAD
规则,这些规则指定了视图上 INSERT
、UPDATE
和 DELETE
命令的替代操作。这些规则将重写命令,通常是重写为更新一个或多个表而不是视图的命令。这是 第 39.4 节 的主题。请注意,这不适用于 MERGE
,它目前不支持除 SELECT
规则之外的目标关系的规则。
请注意,规则首先被评估,在计划和执行之前重写原始查询。因此,如果一个视图在 INSERT
、UPDATE
或 DELETE
上既有 INSTEAD OF
触发器又有规则,那么规则将首先被评估,并且根据结果,可能根本不会使用触发器。
始终最后尝试自动重写简单视图上的 INSERT
、UPDATE
、DELETE
或 MERGE
查询。因此,如果一个视图有规则或触发器,它们将覆盖自动可更新视图的默认行为。
如果视图没有 INSTEAD
规则或 INSTEAD OF
触发器,并且重写器无法自动将查询重写为对基础关系的更新,则会抛出错误,因为执行器无法更新视图本身。
如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步说明的内容,请使用此表单报告文档问题。