2025年9月25日: PostgreSQL 18 发布!
支持的版本:当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本: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

39.2. 视图和规则系统 #

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 规则,允许在视图上执行 INSERTUPDATEDELETE 命令,尽管它缺乏底层存储。这将在下面 第 39.2.4 节中进一步讨论。

39.2.1. SELECT 规则的工作原理 #

规则 ON SELECT 作为最后一步应用于所有查询,即使给出的命令是 INSERTUPDATEDELETE。它们与其他命令类型的规则具有不同的语义,因为它们会就地修改查询树而不是创建新树。因此,我们首先描述 SELECT 规则。

当前,ON SELECT 规则中只能有一个操作,并且必须是无条件的 SELECT 操作,并且是 INSTEAD。为了使规则足够安全,以便向普通用户开放,必须有此限制,它将 ON SELECT 规则限制为像视图一样工作。

本章的示例是两个连接视图,它们进行了一些计算,然后又使用了更多视图。第一个视图中的一个通过为 INSERTUPDATEDELETE 操作添加规则来进行自定义,以便最终结果是一个行为类似于具有一些魔幻功能的真实表的视图。这不是一个简单的入门示例,这使得入门更加困难。但最好有一个示例,分步涵盖所有讨论点,而不是有许多不同的示例,这些示例可能会在脑海中混淆。

我们在前两个规则系统描述中需要的真实表是这些

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;

CREATE VIEW 命令用于 shoelace 视图(这是我们最简单的视图),它将创建一个名为 shoelace 的关系和一个 pg_rewrite 条目,该条目指示每当 shoelace 关系在查询的范围表中被引用时,必须应用一个重写规则。该规则没有规则限定符(稍后讨论,与非 SELECT 规则一起,因为 SELECT 规则目前不能有它们),并且它是 INSTEAD。请注意,规则限定符与查询限定符不同。我们的规则操作具有查询限定符。规则的操作是查询树,它是视图创建命令中 SELECT 语句的副本。

注意

您在 pg_rewrite 条目中看到的用于 NEWOLD 的两个额外范围表条目对于 SELECT 规则不重要。

现在我们填充 unitshoe_datashoelace_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 oldshoelace new。这些条目不直接参与查询,因为子查询的连接树或目标列表未引用它们。重写器使用它们来存储原始引用视图的范围表条目中存在的访问权限检查信息。这样,即使在重写后的查询中未直接使用视图,执行器仍会检查用户是否具有访问视图的适当权限。

这是应用的第一个规则。规则系统将继续检查顶层查询的其余范围表条目(在此示例中没有更多),并且它将递归地检查添加的子查询中的范围表条目,以查看是否有任何条目引用了视图。(但它不会展开 oldnew — 否则我们将发生无限递归!)在此示例中,shoelace_dataunit 没有重写规则,因此重写已完成,以上是提交给规划程序的最终结果。

现在我们要编写一个查询,找出商店中哪些鞋子有匹配的鞋带(颜色和长度),并且完全匹配的鞋带总数大于或等于两个。

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;

类似地,shoeshoelace 的规则被替换到子查询的范围表中,导致一个三层最终查询树

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;

这可能看起来效率不高,但规划器会通过“向上拉取”子查询将此折叠为单层查询树,然后它将像我们手动写出它们一样规划连接。因此,折叠查询树是重写系统不必关心的优化。

39.2.2. 非 SELECT 语句中的视图规则 #

在上述视图规则的描述中,查询树的两个细节没有被触及。这些是命令类型和结果关系。事实上,视图规则不需要命令类型,但结果关系可能会影响查询重写器的工作方式,因为如果结果关系是视图,则需要特别注意。

用于 SELECT 的查询树与任何其他命令的查询树之间只有少数几个区别。显然,它们的命令类型不同,对于非 SELECT 命令,结果关系指向结果应去的范围表条目。其他所有内容都完全相同。因此,具有列 ab 的两个表 t1t2,两个语句的查询树

SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a;

UPDATE t1 SET b = t2.b FROM t2 WHERE t1.a = t2.a;

几乎相同。特别是

  • 范围表包含表 t1t2 的条目。

  • 目标列表包含一个变量,该变量指向表 t2 的范围表条目的列 b

  • 限定表达式将两个范围表条目的列 a 比较为相等。

  • 连接树显示了 t1t2 之间的简单连接。

结果是,这两个查询树都产生类似的执行计划:它们都是两个表的连接。对于 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指向,cmaxxmax 条目被设置为当前命令计数器和当前事务 ID。因此,旧行被隐藏,并且在事务提交后,真空清理程序可以最终删除死行。

了解所有这些之后,我们可以绝对以相同的方式将视图规则应用于任何命令。没有区别。

39.2.3. PostgreSQL 中视图的强大功能 #

以上演示了规则系统如何将视图定义合并到原始查询树中。在第二个示例中,从一个视图进行的简单 SELECT 创建了一个最终查询树,该树是 4 个表的连接(unit 被使用了两次,名称不同)。

使用规则系统实现视图的好处是,规划器拥有有关哪些表需要扫描的所有信息,以及这些表之间的关系,以及来自视图的限制性限定符以及来自原始查询的限定符,所有这些都在一个查询树中。即使原始查询已经是视图的连接,情况也是如此。规划器需要决定执行查询的最佳路径,规划器拥有的信息越多,这个决定就越好。并且 PostgreSQL 中实现的规则系统确保了直到目前为止,关于查询的所有可用信息都是如此。

39.2.4. 更新视图 #

当视图被命名为 INSERTUPDATEDELETEMERGE 的目标关系时会发生什么?执行上述替换将得到一个查询树,其中结果关系指向一个子查询范围表条目,这将不起作用。然而,PostgreSQL 有几种方法可以支持更新视图的表象。按用户体验的复杂性顺序,它们是:自动替换为视图的底层表、执行用户定义的触发器或根据用户定义的规则重写查询。这些选项将在下面讨论。

如果子查询从单个基本关系中选择并且足够简单,重写器可以自动将子查询替换为底层基本关系,以便 INSERTUPDATEDELETEMERGE 以适当的方式应用于基本关系。对于此操作而言 足够简单 的视图称为 自动可更新。有关可以自动更新的视图类型的详细信息,请参阅 CREATE VIEW

或者,操作可以由视图上的用户提供的 INSTEAD OF 触发器处理(请参阅 CREATE TRIGGER)。在这种情况下,重写的工作方式略有不同。对于 INSERT,重写器根本不处理视图,将其保留为查询的结果关系。对于 UPDATEDELETEMERGE,仍然需要展开视图查询以生成命令将尝试更新、删除或合并的 行。因此,视图像往常一样被展开,但查询中会添加另一个未展开的范围表条目来表示视图作为结果关系。

现在出现的问题是如何标识要更新的视图中的行。回想一下,当结果关系是表时,一个特殊的CTID条目被添加到目标列表中以标识要更新的行的物理位置。如果结果关系是视图,则此方法不起作用,因为视图没有CTID,因为它的行没有实际的物理位置。相反,对于 UPDATEDELETEMERGE 操作,会将一个特殊的 wholerow 条目添加到目标列表中,该条目会展开以包含视图中的所有列。执行器使用此值向 INSTEAD OF 触发器提供 行。由触发器根据新旧行值确定要更新的内容。

另一种可能性是用户为视图上的 INSERTUPDATEDELETE 命令定义 INSTEAD 规则,这些规则指定了替代操作。这些规则将重写命令,通常是重写为更新一个或多个表的命令,而不是视图。这是 第 39.4 节的主题。请注意,这不适用于 MERGE,目前 MERGE 不支持目标关系上的规则,除了 SELECT 规则。

请注意,规则会先进行评估,在查询被规划和执行之前重写原始查询。因此,如果视图具有 INSTEAD OF 触发器以及关于 INSERTUPDATEDELETE 的规则,则规则将首先进行评估,并且根据结果,可能根本不使用触发器。

对简单视图上的 INSERTUPDATEDELETEMERGE 查询的自动重写始终最后尝试。因此,如果视图具有规则或触发器,它们将覆盖自动可更新视图的默认行为。

如果没有视图的 INSTEAD 规则或 INSTEAD OF 触发器,并且重写器无法自动将查询重写为对底层基本关系的更新,则会引发错误,因为执行器无法像这样更新视图。

提交更正

如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步说明的内容,请使用 此表单 报告文档问题。