41.2. 视图和规则系统

PostgreSQL 中的视图是使用规则系统实现的。实际上,两者之间基本上没有区别:

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;

因为这正是CREATE VIEW命令在内部执行的操作。这有一些副作用。其中之一是,PostgreSQL 系统目录中有关视图的信息与表中的信息完全相同。因此对于解析器来说,表和视图之间绝对没有区别。他们是同一回事:关系。

41 .2.1. SELECT 规则如何工作

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

当前,ON SELECT规则中只能有一个动作,并且它必须是INSTEAD的无条件SELECT动作。需要使用此限制以使规则足够安全以向普通用户打开它们,并且它限制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;

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

Note

您在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。这些条目不直接参与查询,因为子查询的联接树或目标列表未引用它们。重写器使用它们来存储访问特权检查信息,该信息最初存在于引用该视图的范围表条目中。这样,即使在重写的查询中没有直接使用视图,执行程序仍将检查用户是否具有访问该视图的适当特权。

那是第一条适用的规则。规则系统将 continue 检查顶部查询中的其余范围表条目(在此示例中不再存在),并将递归检查添加的子查询中的范围表条目以查看其中是否有任何引用视图。 (但是它不会扩展oldnew-否则我们将具有无限递归!)在此示例中,没有shoelace_dataunit的重写规则,因此重写已完成,以上是给计划者的最终结果。

现在,我们要编写一个查询,以查找 Store 中当前有哪些鞋带匹配的鞋带(颜色和长度)以及完全匹配的鞋带总数大于或等于 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;

同样,将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;

这看似效率低下,但是计划者将通过“拉起”子查询将其折叠到单级查询树中,然后它将计划联接,就像我们手动将其写出一样。因此,折叠查询树是重写系统不必担心的优化。

41 .2.2. 查看非 SELECT 语句中的规则

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

SELECT的查询树与任何其他命令的查询树之间只有几个区别。显然,它们具有不同的命令类型,并且对于SELECT以外的命令,结果关系指向结果应该到达的范围表条目。其他一切都完全相同。因此,具有两个表t1t2并具有列ab,这两个语句的查询树:

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的事实在 Actuator 中更高处处理,在 Actuator 中,它知道这是UPDATE,并且知道该结果应放入表t1中。但是,哪些行必须用新行替换?

要解决此问题,另一个条目将添加到UPDATE(以及DELETE)语句中的目标列表中:当前的 TuplesID(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。因此,旧行被隐藏,并且在事务提交后,真空吸尘器最终可以删除死行。

知道了所有这些,我们可以简单地以完全相同的方式将视图规则应用于任何命令。没有区别。

41 .2.3. PostgreSQL 中视图的力量

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

使用规则系统实现视图的好处是,计划者可以在一个查询树中获得有关必须扫描哪些表的所有信息,以及这些表之间的关系,视图的限制性条件以及原始查询的条件。 。当原始查询已经是视图联接时,情况仍然如此。计划者必须决定哪个是执行查询的最佳途径,并且计划者拥有的信息越多,这个决定就越好。 PostgreSQL 中实现的规则系统确保了这是到目前为止有关查询的所有可用信息。

41 .2.4. 更新视图

如果将视图命名为INSERTUPDATEDELETE的目标关系,会发生什么?进行上述替换将得到一个查询树,其中结果关系指向子查询范围表条目,这将不起作用。但是,PostgreSQL 可通过多种方式来支持更新视图的外观。

如果子查询从单个基本关系中选择并且足够简单,那么重写器可以自动用基础基本关系替换子查询,以便以适当的方式将INSERTUPDATEDELETE应用于基本关系。为此,“足够简单”的视图称为自动更新。有关可以自动更新的视图类型的详细信息,请参见CREATE VIEW

或者,可以由用户在视图上提供的INSTEAD OF触发器来处理该操作。在这种情况下,重写的工作方式略有不同。对于INSERT,重写器对视图不执行任何操作,将其保留为查询的结果关系。对于UPDATEDELETE,仍然有必要扩展视图查询以生成命令将尝试更新或删除的“旧”行。因此,该视图按正常方式展开,但是另一个未扩展的范围表条目已添加到查询中,以视图的形式将其表示为结果关系。

现在出现的问题是如何识别视图中要更新的行。回想一下,当结果关系是表时,特殊的 CTID 条目将添加到目标列表中,以标识要更新的行的物理位置。如果结果关系是视图,则此方法不起作用,因为视图没有任何 CTID,因为其行没有实际的物理位置。相反,对于UPDATEDELETE操作,将向目标列表添加一个特殊的wholerow条目,该条目会扩展为包括视图中的所有列。执行程序使用此值将“旧”行提供给INSTEAD OF触发器。由触发器决定是否根据旧行和新行的值进行更新。

用户还可以定义INSTEAD规则,这些规则指定视图上INSERTUPDATEDELETE命令的替代动作。这些规则将重写命令,通常将其重写为更新一个或多个表而不是视图的命令。那是Section 41.4的主题。

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

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

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