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 觸發器,並且重寫器無法自動將查詢重寫為對底層基本關係的更新,則會引發錯誤,因為執行器無法像這樣更新檢視。

提交更正

如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步說明的內容,請使用 此表單 報告文件問題。