?
Dieses Dokument verwendet PHP-Handbuch für chinesische Websites Freigeben
PostgreSQL里的視圖是通過規(guī)則系統(tǒng)來實(shí)現(xiàn)的。下面的命令
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;
因?yàn)檫@就是CREATE VIEW命令在內(nèi)部實(shí)際執(zhí)行的內(nèi)容。 這樣做有一些負(fù)作用。其中之一就是在PostgreSQL系統(tǒng)表里的視圖的信息與一般表的信息完全一樣。 所以對于查詢分析器來說,表和視圖之間完全沒有區(qū)別。它們是同樣的事物:關(guān)系。
ON SELECT 的規(guī)則在最后一步應(yīng)用于所有查詢,哪怕給出的是一條INSERT, UPDATE或DELETE命令。 而且與其它規(guī)則有不同的語意,那就是它們在現(xiàn)場修改查詢樹而不是創(chuàng)建一個新的查詢樹。 所以先介紹SELECT規(guī)則。
目前,一個ON SELECT 規(guī)則里只能有一個動作, 而且它必須是一個無條件的INSTEAD的SELECT動作。 有這個限制是為了令規(guī)則安全到普通用戶也可以打開它們,并且它限制ON SELECT規(guī)則使之行為類似試圖。
本文檔的例子是兩個連接視圖,它們做一些運(yùn)算并且因此會涉及到更多視圖的使用。 這兩個視圖之一稍后將利用對INSERT,UPDATE,DELETE操作附加規(guī)則的方法自定義, 這樣做最終的結(jié)果就是這個視圖表現(xiàn)得像一個具有一些特殊功能的真正的表。 這個例子不適合于開始的簡單易懂的例子,從這個例子開始講可能會讓講解變得有些難以理解。 但是用一個覆蓋所有關(guān)鍵點(diǎn)的例子來一步一步討論要比舉很多例子搞亂思維好。
比如,需要一個小巧的min函數(shù)用于返回兩個整數(shù)值中較小的那個。 用下面方法創(chuàng)建它
CREATE FUNCTION min(integer, integer) RETURNS integer AS $$ SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END $$ LANGUAGE SQL STRICT;
在頭兩個規(guī)則系統(tǒng)中我們需要真實(shí)的表的描述是這樣的:
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 );
你可以看到,這些表代表鞋店的數(shù)據(jù)
試圖創(chuàng)建為:
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, min(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;
創(chuàng)建 shoelace 視圖的 CREATE VIEW 命令(也是用到的最簡單的一個)將創(chuàng)建一個shoelace關(guān)系并且在pg_rewrite表里增加一個記錄, 告訴系統(tǒng)有一個重寫規(guī)則應(yīng)用于所有范圍表里引用了shoelace關(guān)系的查詢。 該規(guī)則沒有規(guī)則條件(將在非SELECT規(guī)則討論,因?yàn)槟壳暗?tt class="COMMAND">SELECT規(guī)則不可能有這些東西)并且它是INSTEAD(取代)型的。 要注意規(guī)則條件與查詢條件不一樣。規(guī)則動作有一個查詢條件。 規(guī)則的動作是一個查詢樹,這個查詢是樹視圖創(chuàng)建命令中的SELECT語句的一個拷貝。
Note: 你在pg_rewrite里看到的兩個額外的用于NEW和OLD的范圍表記錄 (因歷史原因,在打印出來的查詢樹里叫NEW和OLD)對SELECT規(guī)則不感興趣。
現(xiàn)在填充unit,shoe_data和shoelace_data,并且在視圖上運(yùn)行一個簡單的查詢:
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 ,所以把它作為解釋基本視圖規(guī)則的命令。 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;
然后把這些交給規(guī)則系統(tǒng)。規(guī)則系統(tǒng)把范圍表(range table)過濾一遍,檢查一下有沒有適用任何關(guān)系的規(guī)則。 當(dāng)為shoelace記錄處理范圍表時(到目前為止唯一的一個), 它會發(fā)現(xiàn)查詢樹里有_RETURN規(guī)則,查詢樹類似下面這樣
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;
為擴(kuò)展該視圖,重寫器簡單地創(chuàng)建一個子查詢范圍表記錄,它包含規(guī)則動作的查詢樹, 然后用這個范圍表記錄取代原先引用視圖的那個。 生成的重寫查詢樹幾乎與你鍵入的那個一樣:
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;
不過還是有一個區(qū)別:子查詢范圍表有兩個額外的記錄shoelace old和shoelace new。 這些記錄并不直接參與查詢,因?yàn)樗鼈儧]有被子查詢的連接樹或者目標(biāo)列表引用。 重寫器用它們存儲最初出現(xiàn)在引用視圖的范圍表里面的訪問權(quán)限檢查。 這樣,執(zhí)行器仍然會檢查該用戶是否有訪問視圖的合適權(quán)限,即使在重寫查詢里面沒有對視圖的直接使用也如此。
這是應(yīng)用的第一個規(guī)則。 規(guī)則系統(tǒng)繼續(xù)檢查頂層查詢里剩下的范圍表記錄(本例中沒有了),并且它在加進(jìn)來的子查詢中遞歸地檢查范圍表記錄, 看看其中有沒有引用視圖的(不過這樣不會擴(kuò)展old或new,否則會無窮遞歸下去!)。 在這個例子中,沒有用于shoelace_data或unit的重寫規(guī)則,所以重寫結(jié)束并且上面的就是給規(guī)劃器的最終結(jié)果。
現(xiàn)在想寫這么一個查詢:這個查詢找出目前在店里有配對鞋帶的鞋子,并且配對的鞋帶數(shù)大于或等于二。
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;
應(yīng)用的第一個規(guī)則將是用于shoe_ready視圖的,結(jié)果是生成查詢樹
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, min(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的規(guī)則替換到子查詢范圍表里,生成一個最終的三層查詢樹:
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, min(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;
最后規(guī)劃器會把這個樹壓縮成一個兩層查詢樹: 最下層的SELECT將"拖到"中間的SELECT中,因?yàn)闆]有必要分別處理它們。 但是中間的SELECT 仍然和頂層的分開,因?yàn)樗奂瘮?shù)。 如果把它們也拉進(jìn)來,那它就會修改最頂層SELECT的行為,那可不是想要的。 不過,壓縮查詢樹是重寫系統(tǒng)自己不需要關(guān)心的優(yōu)化操作。
有兩個查詢樹的細(xì)節(jié)在上面的視圖規(guī)則中沒有涉及到。就是命令類型和結(jié)果關(guān)系。 實(shí)際上,視圖規(guī)則不需要這些信息。
一個 SELECT 的查詢樹和用于其它命令的查詢樹只有少數(shù)幾個區(qū)別。 顯然,它們的命令類型不同并且對于SELECT之外的命令,結(jié)果關(guān)系指向結(jié)果將前往的范圍表入口。 任何其它東西都完全是一樣的。 所以如果有兩個表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的記錄。
目標(biāo)列表包含一個變量,該變量指向表t2的范圍表入口的b字段。
條件表達(dá)式比較兩個范圍的字段a以尋找相等行。
連接樹顯示t1和t2之間的簡單連接
結(jié)果是,兩個查詢樹生成相似的執(zhí)行規(guī)劃:它們都是兩個表的連接。 對于UPDATE語句來說,規(guī)劃器把t1缺失的字段追加到目標(biāo)列因而最終查詢樹看起來像:
UPDATE t1 SET a = t1.a, b = t2.b FROM t2 WHERE t1.a = t2.a;
同時,在連接點(diǎn)運(yùn)行的執(zhí)行器將產(chǎn)生相同的結(jié)果,如下:
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
但是在UPDATE里有點(diǎn)問題:執(zhí)行器不關(guān)心它正在處理的連接結(jié)果的含義是什么。 它只是產(chǎn)生一個行的結(jié)果集。一個是SELECT命令而另一個是UPDATE命令的區(qū)別是由執(zhí)行器的調(diào)用者控制的。 該調(diào)用者這時還知道(查看查詢樹)這是一個UPDATE,而且它還知道結(jié)果要記錄到表t1里去。 但是現(xiàn)有的記錄中的哪一行要被新行取代呢?
要解決這個問題,在UPDATE和DELETE語句的目標(biāo)列表里面增加了另外一個入口。 當(dāng)前的行 ID (CTID)。這是一個有著特殊特性的系統(tǒng)字段。它包含行在文件塊中的塊編號和位置信息。 在已知表的情況下,可以通過CTID檢索最初的需要更新的t1行。 在把CTID加到目標(biāo)列表中去以后,查詢看上去實(shí)際上像這樣:
SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
現(xiàn)在,另一個PostgreSQL 的細(xì)節(jié)進(jìn)入到這個階段里了。 這時,表中的舊行還沒有被覆蓋,這就是為什么ROLLBACK 飛快的原因。 在一個 UPDATE 里,新的結(jié)果行插入到表里(在剝除CTID之后) 并且把CTID指向的舊數(shù)據(jù)行的行頭里面的cmax 和 xmax設(shè)置為當(dāng)前命令計(jì)數(shù)器和當(dāng)前事務(wù) ID 。 這樣舊的行就被隱藏起來并且在事務(wù)提交之后,vacuum 清理器就可以真正把它們刪除掉。
知道了這些,就可以簡單的把視圖的規(guī)則應(yīng)用到任意命令中。視圖和命令沒有區(qū)別。
上面演示了規(guī)則系統(tǒng)如何融合到視圖定義的初始查詢樹中去。 在第二個例子里,一個簡單的對視圖的SELECT創(chuàng)建了一個四表聯(lián)合的查詢樹(unit以不同的名稱用了兩次)。
在規(guī)則系統(tǒng)里實(shí)現(xiàn)視圖的好處是,規(guī)劃器在一個查詢樹里擁有所有信息: 應(yīng)該掃描哪個表+表之間的關(guān)系+視圖的資格限制+初始查詢的資格(條件)。 并且仍然是在最初的查詢已經(jīng)是一個視圖的聯(lián)合的情況下。 現(xiàn)在規(guī)劃器必須決定執(zhí)行查詢的最優(yōu)路徑。 規(guī)劃器擁有越多信息,它的決策就越好。 并且PostgreSQL里的規(guī)則系統(tǒng)的實(shí)現(xiàn)保證這些信息是此時能獲得的有關(guān)該查詢的所有信息。
如果視圖是INSERT,UPDATE,DELETE的目標(biāo)關(guān)系會怎樣? 在完成上面描述的替換之后,就有一個這樣的查詢樹:結(jié)果關(guān)系指向一個是子查詢的范圍表記錄。 這樣可不能運(yùn)行,所以如果重寫器看到自己生成這么個東西,它就拋出一個錯誤。
要修改這個特性,可以定義修改這些命令行為的規(guī)則。這是下一節(jié)的主題。