第四十二章. Postgres 規則系統
內容
Querytree(查詢樹)是什么?
視圖和規則系統
INSERT,UPDATE 和 DELETE 的規則
規則和權限
規則與觸發器之比較
生產規則系統的概念是很簡單的,但是在實際使用的時候會碰到很多細微的問題.這些問題的一部分和 Postgres 規則系統的理論基礎可以在 [Stonebraker et al, ACM, 1990] 找到.
有些數據庫系統定義動態的數據庫規則.這些通常是存儲過程和觸發器,在 Postgres 里是通過函數和觸發器來實現的.
"查詢重寫規則系統"(從現在開始稱之為 "規則系統")是和存儲過程和觸發器完全不同的東西.它把查詢修改為需要考慮規則的形式,然后把修改過的查詢傳遞給查詢優化器執行.這是非常有效的工具并且可以用于許多象查詢語言過程,視圖,和版本等.規則系統的能力在 [Ong and Goh, 1990] 和 [Stonebraker et al, ACM, 1990] 里有討論.
Querytree(查詢樹)是什么?
要理解規則系統如何工作,首先要知道規則何時被激發以及它的輸入和結果是什么.
規則系統位于查詢分析器和優化器之間.以分析器的輸出 -- 一個查詢樹,以及從 pg_rewrite 表里來的重寫規則作為輸入,(重寫規則)也是一個查詢樹,只不過增加了一些擴展信息,然后創建零個或者多個查詢樹作為結果.所以它的輸入和輸出總是那些分析器可以生成的東西,因而任何它(規則系統)看到的東西都是可以用 SQL 語句表達的.
那么什么是 querytree(查詢樹)呢?它是一個 SQL 語句的內部表現形式,這時組成該語句的每個部分都是分別存儲的.當你用調試級別(debuglevel)4 運行 Postgres 后端并且在 SQL 界面交互地輸入查詢命令時可以看到這些查詢樹.在 pg_rewrite 系統表里的規則動作也是以查詢樹的方式存儲的.不過不是用象調試輸出那樣的格式,但內容是完全一樣的.
讀查詢樹的內容需要一定的經驗,我開始在規則系統上干活時經歷了一段很困難的時光.我還記得當初我站在一臺咖啡機面前把杯子當做目標列,水和咖啡粉當作可排列的元素,所有按鈕是合格的表達式來想象查詢樹的情景.因為查詢樹的 SQL 表現是理解規則系統的很重要的部分,這份文檔將不會告訴你如何讀取它們.這篇文檔可能幫助你學習規則系統和它的命名傳統以便于后面的描述.
Querytree(查詢樹)的成員
當我們讀取這份文檔中查詢樹的 SQL 表現時,我們必須能夠識別該語句被分解后放在查詢樹里的成員.查詢樹的成員有
命令類型 ( commandtype )
這是一個簡單的值,說明哪條命令 (SELECT,INSERT,UPDATE,DELETE)生成這個分析樹.
可排列元素 (rangetable)
可排列元素是一個查詢中使用的關系的列表.在 SELECT 語句里是在 FORM 關鍵字后面給出的關系.
每個可排列元素表示一個表或一個視圖,表明是查詢里哪個成員調用了它.在查詢樹里,可排列元素 是用索引而不是用名字引用的,所以這里不用象在 SQL 語句里一樣關心是否有重名問題.這種情 況在引入了規則的可排列元素后可能會發生.本文檔的例子將不討論這種情況.
結果關系(resultrelation)
這是一個可排列元素的索引,用于標識查詢結果之間的關系.
SELECT 查詢通常沒有結果集關系.SELECT INTO 幾乎等于一個 CREATE TABLE,INSERT ... SELECT 序列,所以這里我們就不單獨討論了.
在 INSERT,UPDATE 和 DELETE 查詢里,結果關系(resultrelation )是更改發生影響的表(或視圖!).
目標列 (targetlist)
目標列是一列定義查詢結果的表達式.在 SELECT 的情況下,這些表達式是就是構建查詢的最終輸出的東西.它們是位于 SELECT 和 FROM 關鍵字之間的表達式 (* 只是表明一個關系的所有字段的縮寫).
DELETE 不需要目標列是因為它們不產生任何結果.實際上優化器會向空目標列增加一個特殊入口.但這是在規則系統之后并且我們將稍后討論.對于規則系統而言,目標列是空的.
在 INSERT 查詢里面,目標列描述了應該進入結果集的新行.忽略的字段將由優化器自動賦予一個常量NULL.這些就是在 VALUES 子句里的表達式或在 INSERT ... SELECT 語句里的話 SELECT 子句.
在 UPDATE 查詢里,它(目標列)描述應該替換舊行的新行.這時,優化器將通過插入從舊行抽取數據到新行的表達式向新行追加缺失的字段.并且它也會象在 DELETE 里那樣增加特殊的入口.它是從查詢的 SET attribute = expression 部分抽取的表達式.
目標列里的每個元素都包含著一個可以為常量,可以為一個指向某個可排列元素里面的關系的字段的變量指針,可以為一個由函數調用,常量,變量,操作符等構成的表達式樹的表達式.
資格 (qualification)
查詢資格是一個表達式,它非常類似那些包含在目標列里的條目.這個表達式的值是一個布爾值,通過此值來判斷對最終結果行是否要執操作(INSERT,UPDATE,DELETE 或 SELECT).它是一個 SQL 語句 的 WHERE 子句.
其他 (others)
查詢樹的其他部分,像 ORDER BY 子句,我們不準備在這里討論.規則系統在附加規則時將在那里(ORDER BY 子句)替換規則,但是這對于規則系統的基本原理并沒有多大關系.當 GROUP BY 在視圖定義中出現時是一個特例,仍然需要在我們的文檔里做些說明.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
視圖和規則系統
Postgres 里視圖的實現
Postgres 里的視圖是通過規則系統來實現的.實際上下面的命令
CREATE VIEW myview AS SELECT * FROM mytab;
和下面兩條命令
CREATE TABLE myview (same attribute list as for mytab);
CREATE RULE "_RETmyview" AS ON SELECT TO myview DO INSTEAD
SELECT * FROM mytab;
之間絕對沒有區別,因為這就是 CREATE VIEW 命令在內部實際執行的內容.這樣做有一些負作用.其中之一就是在 Postgres 系統表里的視圖的信息與一般表的信息完全一樣.所以對于查詢分析器來說,表和視圖之間完全沒有區別.它們是同樣的事物--關系.這就是目前很重要的一點.
SELECT 規則如何工作
ON SELECT 的規則在最后一步應用于所有查詢,哪怕給出的命令是一條 INSERT,UPDATE 或 DELETE.而且與其他(規則)有不同的語意,那就是它們在實地修改分析樹而不是創建一個新的(分析樹).所以我們先介紹 SELECT 的規則.
目前,這里只可能發生一個動作(action)而且它必須是一個 INSTEAD (取代了)的 SELECT 動作.有這個限制是為了令規則安全到普通用戶也可以打開它們,并且它對真正的視圖規則做 ON SELECT 規則限制.
本文檔的例子是兩個聯合視圖,它們做一些運算并且會涉及到更多視圖的使用.這兩個視圖之一稍后將利用對 INSERT,UPDATE 和 DELETE 操作附加規則的方法客戶化,這樣做最終的結果就會是這個視圖表現得象一個具有一些特殊功能的真正的表.這可不是一個適合于開始的簡單易懂的例子,從這個例子開始講可能會讓我們的講解變得有些難以理解.但是我們認為用一個覆蓋所有關鍵點的例子來一步一步討論要比舉很多例子搞亂思維好多了.
在本例子中用到的數據庫名是 al_bundy.你很快就會明白為什么叫這個名字.而且這個例子需要安裝過程語言 PL/pgSQL ,因為我們需要一個小巧的 min() 函數用于返回兩個整數值中的小的那個.我們用下面方法創建它
CREATE FUNCTION min(integer, integer) RETURNS integer AS
'BEGIN
IF $1 < $2 THEN
RETURN $1;
END IF;
RETURN $2;
END;'
LANGUAGE 'plpgsql';
我們頭兩個規則系統要用到的真實的表的描述如下:
CREATE TABLE shoe_data (
shoename char(10), -- primary key
sh_avail integer, -- available # of pairs
slcolor char(10), -- preferred shoelace color
slminlen float, -- miminum shoelace length
slmaxlen float, -- maximum shoelace length
slunit char(8) -- length unit
);
CREATE TABLE shoelace_data (
sl_name char(10), -- primary key
sl_avail integer, -- available # of pairs
sl_color char(10), -- shoelace color
sl_len float, -- shoelace length
sl_unit char(8) -- length unit
);
CREATE TABLE unit (
un_name char(8), -- the primary key
un_fact float -- factor to transform to cm
);
我想我們都需要穿鞋子,因而上面這些數據都是很有用的數據.當然,有那些不需要鞋帶的鞋子,但是不會讓 AL 的生活變得更輕松,所以我們忽略之.
視圖創建為
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;
用于 shoelace 的 CREATE VIEW 命令(也是我們用到的最簡單的一個)將創建一個關系/表 -- 鞋帶(relation shoelace )并且在 pg_rewrite 表里增加一個記錄,告訴系統有一個重寫規則應用于所有索引了鞋帶關系(relation shoelace)的查詢.該規則沒有規則資格(將在非 SELECT 規則討論,因為目前的 SELECT 規則不可能有這些東西)并且它是 INSTEAD (取代)型的.要注意規則資格與查詢資格不一樣!這個規則動作(action)有一個資格.
規則動作(action)是一個查詢樹,實際上是在創建視圖的命令里的 SELECT 語句的一個拷貝.
注意:你在表 pg_rewrite 里看到的兩個額外的用于 NEW 和 OLD 范圍表的記錄(因歷史原因,在打印出來的查詢樹里叫 *NEW* 和 *CURRENT* )對 SELECT 規則不感興趣.
現在我們填充 unit,shoe_data 和 shoelace_data 并且 Al 生平第一次鍵入了 SELECT 命令:
al_bundy=> INSERT INTO unit VALUES ('cm', 1.0);
al_bundy=> INSERT INTO unit VALUES ('m', 100.0);
al_bundy=> INSERT INTO unit VALUES ('inch', 2.54);
al_bundy=>
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh1', 2, 'black', 70.0, 90.0, 'cm');
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh2', 0, 'black', 30.0, 40.0, 'inch');
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh3', 4, 'brown', 50.0, 65.0, 'cm');
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh4', 3, 'brown', 40.0, 50.0, 'inch');
al_bundy=>
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl1', 5, 'black', 80.0, 'cm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl2', 6, 'black', 100.0, 'cm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl3', 0, 'black', 35.0 , 'inch');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl4', 8, 'black', 40.0 , 'inch');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl5', 4, 'brown', 1.0 , 'm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl6', 0, 'brown', 0.9 , 'm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl7', 7, 'brown', 60 , 'cm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl8', 1, 'brown', 40 , 'inch');
al_bundy=>
al_bundy=> 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)
這是 Al 可以在我們的視圖上做的最簡單的 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;
然后把這些交給規則系統.規則系統把可排列元素(rangetable)過濾一遍,檢查一下在 pg_rewrite 表里面有沒有適用該關系的任何規則.當為 shoelace 處理可排列元素時(到目前為止唯一的一個),它會發現分析樹里有規則 '_RETshoelace'
SELECT s.sl_name, s.sl_avail,
s.sl_color, s.sl_len, s.sl_unit,
float8mul(s.sl_len, u.un_fact) AS sl_len_cm
FROM shoelace *OLD*, shoelace *NEW*,
shoelace_data s, unit u
WHERE bpchareq(s.sl_unit, u.un_name);
注意分析器已經把(SQL里的)計算和資格換成了相應的函數.但實際上這沒有改變什么.重寫的第一步是把兩個可排列元素歸并在一起.結果生成的分析樹是
SELECT shoelace.sl_name, shoelace.sl_avail,
shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit, shoelace.sl_len_cm
FROM shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data s,
unit u;
第二步把資格的規則動作追加到分析樹里面去,結果是
SELECT shoelace.sl_name, shoelace.sl_avail,
shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit, shoelace.sl_len_cm
FROM shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data s,
unit u
WHERE bpchareq(s.sl_unit, u.un_name);
第三步把分析樹里的所有變量用規則動作里對應的目標列表達式替換掉,這些變量是引用了可排列元素(目前來說是正在處理的 shoelace )的變量.這就生成了最后的查詢
SELECT s.sl_name, s.sl_avail,
s.sl_color, s.sl_len,
s.sl_unit, float8mul(s.sl_len, u.un_fact) AS sl_len_cm
FROM shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data s,
unit u
WHERE bpchareq(s.sl_unit, u.un_name);
把這些轉換回人類可能使用的 SQL 語句
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;
這是應用的第一個規則.當做完這些后,可排列元素就增加了.所以規則系統繼續檢查范圍表入口.下一個是第2個(shoelace *OLD*). shoelace (鞋帶)關系有一個規則,但這個可排列元素沒有被任何分析樹里的變量引用,所以被忽略.因為所有剩下的可排列元素入口要么是在 pg_rewrite 表里面沒有記錄,要么是沒有引用,因而到達重排列元素結尾.所以重寫結束,因而上面的結果就是給優化器的最終結果.優化器忽略那些在分析樹里多余的沒有被變量引用的可排列元素,并且由規劃器/優化器生成的(運行)規劃將和 Al 在上面鍵入的 SELECT 查詢一樣,而不是視圖選擇.
現在我們讓 Al 面對這樣一個問題:Blues 兄弟到了他的鞋店想買一雙新鞋,而且 Blues 兄弟想買一樣的鞋子.并且要立即就穿上,所以他們還需要鞋帶.
Al 需要知道鞋店里目前那種鞋有合適的鞋帶(顏色和尺寸)以及完全一樣的配置的庫存是否大于或等于兩雙.我們告訴他如何做,然后他問他的數據庫:
al_bundy=> 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)
Al 是鞋的專家,知道只有 sh1 的類型會適用(sl7鞋帶是棕色的,而與棕色的鞋帶匹配的鞋子是 Blues 兄弟從來不穿的).
這回分析器的輸出是分析樹
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 int4ge(shoe_ready.total_avail, 2);
應用的第一個規則將是用于 shoe_ready 關系的,結果是生成分析樹
SELECT rsh.shoename, rsh.sh_avail,
rsl.sl_name, rsl.sl_avail,
min(rsh.sh_avail, rsl.sl_avail) AS total_avail
FROM shoe_ready shoe_ready, shoe_ready *OLD*,
shoe_ready *NEW*, shoe rsh,
shoelace rsl
WHERE int4ge(min(rsh.sh_avail, rsl.sl_avail), 2)
AND (bpchareq(rsl.sl_color, rsh.slcolor)
AND float8ge(rsl.sl_len_cm, rsh.slminlen_cm)
AND float8le(rsl.sl_len_cm, rsh.slmaxlen_cm)
);
實際上,資格/條件里的 AND 子句將是擁有左右表達式的操作符節點.但那樣會把可讀性降低,而且還有更多規則要附加.所以我只是把它們放在一些圓括號里,將它們按出現順序分成邏輯單元,然后我們繼續對付用于 shoe (鞋)關系的規則,因為它是引用了的下一個可排列元素并且有一條規則.應用規則后的結果是
SELECT sh.shoename, sh.sh_avail,
rsl.sl_name, rsl.sl_avail,
min(sh.sh_avail, rsl.sl_avail) AS total_avail,
FROM shoe_ready shoe_ready, shoe_ready *OLD*,
shoe_ready *NEW*, shoe rsh,
shoelace rsl, shoe *OLD*,
shoe *NEW*, shoe_data sh,
unit un
WHERE (int4ge(min(sh.sh_avail, rsl.sl_avail), 2)
AND (bpchareq(rsl.sl_color, sh.slcolor)
AND float8ge(rsl.sl_len_cm,
float8mul(sh.slminlen, un.un_fact))
AND float8le(rsl.sl_len_cm,
float8mul(sh.slmaxlen, un.un_fact))
)
)
AND bpchareq(sh.slunit, un.un_name);
最后,我們把已經熟知的用于 shoelace (鞋帶)的規則附加上去(這回我們在一個更復雜的分析樹上)得到
SELECT sh.shoename, sh.sh_avail,
s.sl_name, s.sl_avail,
min(sh.sh_avail, s.sl_avail) AS total_avail
FROM shoe_ready shoe_ready, shoe_ready *OLD*,
shoe_ready *NEW*, shoe rsh,
shoelace rsl, shoe *OLD*,
shoe *NEW*, shoe_data sh,
unit un, shoelace *OLD*,
shoelace *NEW*, shoelace_data s,
unit u
WHERE ( (int4ge(min(sh.sh_avail, s.sl_avail), 2)
AND (bpchareq(s.sl_color, sh.slcolor)
AND float8ge(float8mul(s.sl_len, u.un_fact),
float8mul(sh.slminlen, un.un_fact))
AND float8le(float8mul(s.sl_len, u.un_fact),
float8mul(sh.slmaxlen, un.un_fact))
)
)
AND bpchareq(sh.slunit, un.un_name)
)
AND bpchareq(s.sl_unit, u.un_name);
同樣,我們把它歸結為一個與最終的規則系統輸出等效的真實 SQL 語句:
SELECT sh.shoename, sh.sh_avail,
s.sl_name, s.sl_avail,
min(sh.sh_avail, s.sl_avail) AS total_avail
FROM shoe_data sh, shoelace_data s, unit u, unit un
WHERE min(sh.sh_avail, s.sl_avail) >= 2
AND s.sl_color = sh.slcolor
AND s.sl_len * u.un_fact >= sh.slminlen * un.un_fact
AND s.sl_len * u.un_fact <= sh.slmaxlen * un.un_fact
AND sh.sl_unit = un.un_name
AND s.sl_unit = u.un_name;
遞歸的處理規則將把一個從視圖的 SELECT 改寫為一個分析樹,這樣做等效于如果沒有視圖存在時 Al 不得不鍵入的(SQL)命令.
注意: 目前規則系統中沒有用于視圖規則遞歸終止機制(只有用于其他規則的).這一點不會造成太大的損害,因為把這個(規則)無限循環(把后端摧毀,直到耗盡內存)的唯一方法是創建表然后手工用 CREATE RULE 命令創建視圖規則,這個規則是這樣的:一個從其他(表/視圖)選擇(select)的視圖選擇(select)了它自身.如果使用了 CREATE VIEW ,這一點是永遠不會發生的,因為第二個關系不存在因而第一個視圖不能從第二個里面選擇(select).
非 SELECT 語句的視圖規則
有兩個分析樹的細節我們在上面的視圖規則中沒有涉及到.就是命令類型和結果關系.實際上,視圖規則不需要這些信息.
一個 SELECT 的分析樹和用于其他命令的分析樹只有少數幾個區別.顯然它們有另一個命令類型并且這回結果關系指向生成結果的可排列元素入口.任何其東西都完全是一樣的.所以如果有兩個表 t1 和 t2 分別有字段 a 和 b ,下面兩個語句的分析樹
SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a;
UPDATE t1 SET b = t2.b WHERE t1.a = t2.a;
幾乎是一樣的.
可排列元素包含表 t1 和 t2 的記錄.
目標列包含一個指向字段表 t2 的可排列元素 b 的變量.
格表達式比較兩個表的字段 a 以尋找相等(行).
結果是,兩個分析樹生成相似的執行規劃.它們都是兩個表的聯合.對于 UPDATE 語句來說,優化器把 t1 缺失的字段追加到目標列因而最終分析樹看起來象
UPDATE t1 SET a = t1.a, b = t2.b WHERE t1.a = t2.a;
因此執行器在聯合上運行的結果和下面語句
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
是完全一樣的.但是在 UPDATE 里有點問題.執行器不關心它正在處理的從聯合出來的結果的含義是什么.它只是產生一個行的結果集.一個是 SELECT 命令而另一個是 UPDATE 命令的區別是由執行器的調用者控制的.該調用者這時還知道(查看分析樹)這是一個 UPDATE,而且它還知道結果要記錄到表 t1 里去.但是現有的666行記錄中的哪一行要被新行取代呢?被執行的(查詢)規劃是一個帶有資格(條件)的聯合,該聯合可能以未知順序生成 0 到 666 間任意數量的行.
要解決這個問題,在 UPDATE 和 DELETE 語句的目標列表里面增加了另外一個入口.當前的記錄 ID(ctid).這是一個有著特殊特性的系統字段.它包含行在(存儲)塊中的(存儲)塊數和位置信息.在已知表的情況下,ctid 可以通過簡單地查找某一數據塊在一個 1.5GB 大小的包含成百萬條記錄的表里面查找某一特定行.在把 ctid 加到目標列表中去以后,最終的結果可以定義為
SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
現在,另一個 Postgres 的細節進入到這個階段里了.這時,表的行還沒有被覆蓋,這就是為什么 ABORT TRANSACTION 速度快的原因.在一個 UPDATE 里,新的結果行插入到表里(在通過 ctid 查找之后)并且把 ctid 指向的 cmax 和 xmax 入口的行的記錄頭設置為當前命令計數器和當前交易ID.這樣舊的行就被隱藏起來并且在事務提交之后"吸塵器"(vacumm cleaner)就可以真正把它們刪除掉.
知道了這些,我們就可以簡單的把視圖的規則應用到任意命令中.它們(視圖和命令)沒有區別.
Postgres 里視圖的強大能力
上面演示了規則系統如何融合到視圖定義的初始分析樹中去.在第二個例子里,一個簡單的對視圖的 SELECT 創建了一個4個表聯合的分析樹(unit 以不同的名稱用了兩次).
益處
在規則系統里實現視圖的好處是優化器在一個分析樹里擁有所有信息:應該掃描哪個表 + 表之間的關系 + 視圖的資格限制 + 初始查詢的資格(條件).并且依然是在最初的查詢已經是一個視圖的聯合的情況下.現在優化器必須決定執行查詢的最優路徑.優化器擁有越多信息,它的決策就越好.并且 Postgres 里的規則系統的實現保證這些信息是目前能獲得的所有信息.
考慮
很長一段時間里,Postgres 規則系統被認為是有問題的.規則的使用是不被推薦的而且能工作的部分只有視圖規則.而且這些視圖規則還常常犯毛病,因為規則系統不能在除 SELECT 語句外的語句里應用它們(例如用從一個視圖來的數據 UPDATE 就不能工作).
在那段時間里,開發工作繼續進行,許多新特性加入到分析器和優化器里.規則系統的功能越來越陳舊而且越來越難以修復它們.
從 6.4 起,某個人(譯注:感謝 Jan )關起門,深吸一口氣把所有這些爛東西徹底修理了一便.結果就是本章描述的規則系統.但是還有一些無法處理的構造和一些失效的地方,主要原因是這些東西現在不被 Postgres 查詢優化器支持.
帶聚集字段的視圖有很大問題.在資格列里的聚集表達式必須通過子查詢使用.目前不可能做兩個視圖的聯合查詢 -- 每個都有一個聚集字段,并且與資格列里的兩個聚集的結果進行比較.但我們可能把這些聚集表達式放到函數里,通過合適的參數在視圖定義中使用它們.
聯合(union)的視圖當前不被支持.盡管我們很容易把一個簡單的SELECT 重寫成聯合(union).但是如果該視圖是一個正在做更新的聯合的一部分時就會有麻煩.
視圖里的 ORDER BY 子句不被支持.
視圖里的 DISTINCT 不被支持.
為什么優化器不能處理那些分析器因 SQL 語法限制生成的"不應該生成"分析樹?我們也沒有充分的理由.作者希望這些問題在將來會消失.
如此實現的副作用
使用上面描述的規則系統來實現視圖有著有趣的副作用.下面的(命令)看起來不會工作:
al_bundy=> INSERT INTO shoe (shoename, sh_avail, slcolor)
al_bundy-> VALUES ('sh5', 0, 'black');
INSERT 20128 1
al_bundy=> SELECT shoename, sh_avail, slcolor FROM shoe_data;
shoename |sh_avail|slcolor
----------+--------+----------
sh1 | 2|black
sh3 | 4|brown
sh2 | 0|black
sh4 | 3|brown
(4 rows)
有趣的事情是 INSERT 的返回碼給我們一個對象標識(OID)并且告訴我們插入了一行.但該行沒有在 shoe_data 里出現.往數據庫目錄里看時我們可以發現,用于視圖關系 shoe 的數據庫文件看來現在有了數據塊.實際情況正是如此.
我們還可以使用一條 DELETE 命令,如果該命令沒有(資格)條件,它會告訴我們有一行被刪除了并且下一次清理時將把文件復位為零尺寸.
這種現象的原因是 INSERT 生成的分析樹沒有在任何變量里引用 shoe (鞋)關系.目標列表只包含常量值.所以不會附加任何規則,查詢不加修改地進入執行插入該行. DELETE 時完全一樣.
要改變這些問題,我們可以定義一些規則用以改變 非-SELECT 查詢的特性.這是下一章的內容.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
INSERT,UPDATE 和 DELETE 的規則
與 View Rules 的區別
定義在 ON INSERT,UPDATE 和 DELETE 的規則與前一章描述的視圖規則完全不同.首先,他們的 CREATE RULE 命令允許更多:
它們可以沒有動作(action).
它們可以有多個動作(action).
關鍵字 INSTEAD 是可選的.
偽關系 NEW 和 OLD 變得可用.
它們可以有規則資格條件.
第二,它們不是實地修改分析樹.它們是創建零個或多個新分析樹并且可以把最先的那個仍掉.
這些規則是如何工作的
把下面語法
CREATE RULE rule_name AS ON event
TO object [WHERE rule_qualification]
DO [INSTEAD] [action | (actions) | NOTHING];
牢牢記住.下面,"update rules" 意思是定義在 ON INSERT,UPDATE 或 DELETE 上的規則.
當分析樹的結果關系和命令類型與 CREATE RULE 命令里給出的對象和事件一樣的話,規則系統就把更新規則(update rules)應用上去.對于更新規則(update rules),規則系統創建一個分析樹列表.一開始分析樹是空的.這里可以有零個(NOTHING 關鍵字),一個或多個動作.為簡單起見,我們看一眼一個只有一個動作(action)的規則.這個規則可以有一個資格(條件)或沒有并且它可以是 INSTEAD 或反之.
何為規則資格?它是一個限制條件,告訴規則動作(action)什么時候要做,什么時候不用做.這個資格(條件)可以只引用 NEW 和/或 OLD 偽關系--它們是作為對象給出的基本關系(但是有著特殊含義).
所以,對這個一個動作(action)的規則生成分析樹,有下面四種情況.
沒有資格(條件)和沒有 INSTEAD:
從規則動作(action)來的分析樹,已經在最初的分析樹上追加了資格(條件).
沒有資格(條件)但有 INSTEAD:
從規則動作(action)來的分析樹,已經在最初的分析樹上追加了資格(條件).
有資格(條件)但沒有 INSTEAD:
從規則動作(action)來的分析樹,追加了規則資格(條件)和在最初的分析樹的資格(條件).
有資格(條件)也有 INSTEAD:
從規則動作(action)來的分析樹,追加了規則資格(條件)和在最初的分析樹的資格(條件).
最初的分析樹,增加了相反的規則資格(條件).
最后,如果規則不是 INSTEAD,最初的未修改的分析樹被加入到列表.因為只有資格(條件)規則已經在初始的分析樹里面,所以我們最終得到最多有兩個分析樹的單動作(action)規則。
從規則動作生成的分析樹被再次送到重寫系統并且可能應用更多的規則,結果是更多的或更少的分析樹.所以規則動作里的分析樹必須是另一個命令類型或另一個結果關系.否則這樣的遞歸過程就會沒完沒了.現在有一個編譯級的遞歸限制是10個語句.如果10次遞歸之后還有需要應用的更新規則(update rules),規則系統就認為是一個多規則的循環而退出事務.
在 pg_rewrite 系統表里的分析樹的動作(action)只是模板.因為他們可以引用 NEW 和 OLD 的可排列元素,在使用它們之前必須做一些調整.對于任何對 NEW 的引用,都要先在初始查詢的目標列中搜索對應的條目.如果找到,把該條目表達式放到引用里.否則 NEW 和 OLD 的含義一樣.任何用于 OLD 的引用都用結果關系的可排列元素的引用替換.
循序漸進的第一個規則
我們希望跟蹤 shoelace_data 關系中的 sl_avail 字段.所以我們設置一個日志表和一條規則,這條規則每次在用 UPDATE 更新 shoelace_data 表時都要往數據庫里寫一條記錄.
CREATE TABLE shoelace_log (
sl_name char(10), -- shoelace changed
sl_avail integer, -- new available value
log_who name, -- who did it
log_when datetime -- when
);
CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data
WHERE NEW.sl_avail != OLD.sl_avail
DO INSERT INTO shoelace_log VALUES (
NEW.sl_name,
NEW.sl_avail,
getpgusername(),
'now'::text
);
一個有趣的細節是在規則 INSERT 動作(action)里把 'now' 轉換成類型 text.如果不這樣做,分析器將在 CREATE RULE 時,將看到 shoelace_log 里的目標類型是日期并且將試圖從中成功地獲取一個常量.所以一個常量日期值將被存儲,結果是所有日志的記錄都將是 CREATE RULE 語句的執行時間.這可不是我們想要的.類型轉換將導致分析器從中構建一個日期('now'::text)并且在規則執行時將被計算.
現在 Al 鍵入
al_bundy=> UPDATE shoelace_data SET sl_avail = 6
al_bundy-> WHERE sl_name = 'sl7';
然后我們看看日志表.
al_bundy=> SELECT * FROM shoelace_log;
sl_name |sl_avail|log_who|log_when
----------+--------+-------+--------------------------------
sl7 | 6|Al |Tue Oct 20 16:14:45 1998 MET DST
(1 row)
這是我們想要的.后端發生的事情如下.分析器創建分析樹(這回最初的分析樹的部分寫成高亮顯示,因為操作的基礎是用于更新規則的規則動作).
UPDATE shoelace_data SET sl_avail = 6
FROM shoelace_data shoelace_data
WHERE bpchareq(shoelace_data.sl_name, 'sl7');
這里是一個規則 'log_shoelace' 用于 ON UPDATE 帶著規則資格表達式
int4ne(NEW.sl_avail, OLD.sl_avail)
和一個動作
INSERT INTO shoelace_log SELECT
*NEW*.sl_name, *NEW*.sl_avail,
getpgusername(), datetime('now'::text)
FROM shoelace_data *NEW*, shoelace_data *OLD*,
shoelace_log shoelace_log;
不要相信 pg_rules 系統視圖的輸出.它是用于下面的特殊場合的:在 INSERT 中只引用了 NEW 和 OLD 并且輸出 INSERT 的 VALUES 格式.實際上在分析樹級別上,INSERT ... VALUES 和 INSERT ... SELECT 語句沒有區別.它們都有可排列元素,目標列表以及還可能有資格(條件)等.優化器稍后將決定是否為該分析樹創建一個有關類型結果,序列號掃描,索引掃面,聯合或其他關系的的執行規劃.如果在分析樹里沒有可排列元素的引用,它就變成了一個結果執行規劃(INSERT ... VALUES 的情況).上面的規則動作(action)可以真實地在兩種變種里生成.
該規則是一個有資格(條件)的非 INSTEAD 規則,所以規則系統必須返回兩個分析樹.更改過的規則動作(action)和原始分析樹.在第一步里,原始查詢的可排列元素集成到規則動作(action)分析樹里.生成
INSERT INTO shoelace_log SELECT
*NEW*.sl_name, *NEW*.sl_avai,
getpgusername(), datetime('now'::text)
FROM shoelace_data shoelace_data, shoelace_data *NEW*,
shoelace_data *OLD*, shoelace_log shoelace_log;
第二步把規則資格(條件)增加進去,所以結果集限制為 sl_avail 改變了的行.
INSERT INTO shoelace_log SELECT
*NEW*.sl_name, *NEW*.sl_avai,
getpgusername(), datetime('now'::text)
FROM shoelace_data shoelace_data, shoelace_data *NEW*,
shoelace_data *OLD*, shoelace_log shoelace_log
WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail);
第三步把原始分析樹的資格(條件)加進去,把結果集進一步限制成只有被初始分析樹改變的行.
INSERT INTO shoelace_log SELECT
*NEW*.sl_name, *NEW*.sl_avai,
getpgusername(), datetime('now'::text)
FROM shoelace_data shoelace_data, shoelace_data *NEW*,
shoelace_data *OLD*, shoelace_log shoelace_log
WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail)
AND bpchareq(shoelace_data.sl_name, 'sl7');
第四步把 NEW 引用替換為從原始分析樹的目標列來的或從結果關系來的匹配的變量引用.
INSERT INTO shoelace_log SELECT
shoelace_data.sl_name, 6,
getpgusername(), datetime('now'::text)
FROM shoelace_data shoelace_data, shoelace_data *NEW*,
shoelace_data *OLD*, shoelace_log shoelace_log
WHERE int4ne(6, *OLD*.sl_avail)
AND bpchareq(shoelace_data.sl_name, 'sl7');
第五步用 OLD 引用把結果關系的引用替換掉.
INSERT INTO shoelace_log SELECT
shoelace_data.sl_name, 6,
getpgusername(), datetime('now'::text)
FROM shoelace_data shoelace_data, shoelace_data *NEW*,
shoelace_data *OLD*, shoelace_log shoelace_log
WHERE int4ne(6, shoelace_data.sl_avail)
AND bpchareq(shoelace_data.sl_name, 'sl7');
這就成了.所以最大限度的把規則系統的返回縮減后的結果是兩個分析樹的列表,與下面語句相同:
INSERT INTO shoelace_log SELECT
shoelace_data.sl_name, 6,
getpgusername(), 'now'
FROM shoelace_data
WHERE 6 != shoelace_data.sl_avail
AND shoelace_data.sl_name = 'sl7';
UPDATE shoelace_data SET sl_avail = 6
WHERE sl_name = 'sl7';
這就是執行的順序以及規則定義的東西.做的替換和追加的資格(條件)用以確保如果原始的查詢是下面這樣
UPDATE shoelace_data SET sl_color = 'green'
WHERE sl_name = 'sl7';
就不會有日期記錄寫到表里,因為這回原始分析樹不包含有關 sl_avail 的目標列表,NEW.sl_avail 將被 shoelace_data.sl_avail 代替,結果是下面的查詢
INSERT INTO shoelace_log SELECT
shoelace_data.sl_name, shoelace_data.sl_avail,
getpgusername(), 'now'
FROM shoelace_data
WHERE shoelace_data.sl_avail != shoelace_data.sl_avail
AND shoelace_data.sl_name = 'sl7';
并且資格(條件)將永遠不可能是真值.因為在分析樹級別上 INSERT ... SELECT 和 INSERT ... VALUES 之間沒有區別,所以如果原始查詢更改多行的話,(規則)仍將生效.所以如果 Al 寫出下面命令
UPDATE shoelace_data SET sl_avail = 0
WHERE sl_color = 'black';
實際上有四行被更新(sl1,sl2,sl3 和 sl4).但 sl3 已經是 sl_avail = 0.這回,原始的分析樹資格(條件)已經不一樣了,結果是生成下面的分析樹
INSERT INTO shoelace_log SELECT
shoelace_data.sl_name, 0,
getpgusername(), 'now'
FROM shoelace_data
WHERE 0 != shoelace_data.sl_avail
AND shoelace_data.sl_color = 'black';
這個分析樹將肯定插入三個新的日志記錄.這也是完全正確的.
重要的是原始分析樹最后執行.Postgres 的"交警" 在兩個分析樹的執行間做一次命令計數器增一的動作,所以第二個(分析樹)可以看到第一個(分析樹)所做的改變.如果 UPDATE 將先被執行,所有的行都已經設為零,所以記日志的 INSERT 將不能找到任何行是符合 0 != shoelace_data.sl_avail 條件的.
與視圖共存
一個簡單的保護視圖關系,使其避免我們曾提到的有人可以在其中 INSERT,UPDATE 和 DELETE 不可見的數據的方法是讓那些分析樹被丟棄.我們創建下面規則
CREATE RULE shoe_ins_protect AS ON INSERT TO shoe
DO INSTEAD NOTHING;
CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe
DO INSTEAD NOTHING;
CREATE RULE shoe_del_protect AS ON DELETE TO shoe
DO INSTEAD NOTHING;
如果 Al 現在試圖對視圖關系 shoe 做上面的任何操作,規則系統將應用這些規則.因為這些規則沒有動作而且是 INSTEAD,結果是生成的分析樹將是空的并且整個查詢將變得空空如也,因為經過規則系統處理后沒有什么東西剩下來用于優化或執行了.
注意:這個方法可能會令前端困惑,因為在數據庫里完全沒有任何事情發生,因而后端對查詢將不返回任何信息.在 libqp 里甚至是一個 PGRES_EMPTY_QUERY 或其他的信息也不返回.在 psql 里,什么也沒發生.這些將在以后修改.
一個更復雜的使用規則系統的方法是用規則系統創建一個重寫分析樹的規則,使分析樹對真實的表進行正確的操作.要在視圖 shoelace 上做這個工作,我們創建下面規則:
CREATE RULE shoelace_ins AS ON INSERT TO shoelace
DO INSTEAD
INSERT INTO shoelace_data VALUES (
NEW.sl_name,
NEW.sl_avail,
NEW.sl_color,
NEW.sl_len,
NEW.sl_unit);
CREATE RULE shoelace_upd AS ON UPDATE TO shoelace
DO INSTEAD
UPDATE shoelace_data SET
sl_name = NEW.sl_name,
sl_avail = NEW.sl_avail,
sl_color = NEW.sl_color,
sl_len = NEW.sl_len,
sl_unit = NEW.sl_unit
WHERE sl_name = OLD.sl_name;
CREATE RULE shoelace_del AS ON DELETE TO shoelace
DO INSTEAD
DELETE FROM shoelace_data
WHERE sl_name = OLD.sl_name;
現在有一包鞋帶到達 Al 的商店,而且這是一大筆到貨.Al 并不長于計算,所以我們不想讓他手工更新鞋帶視圖.取而代之的是我們創建了兩個小表,一個是我們可以從到貨清單中插入東西,另一個是一個特殊的技巧.創建這些的命令如下:
CREATE TABLE shoelace_arrive (
arr_name char(10),
arr_quant integer
);
CREATE TABLE shoelace_ok (
ok_name char(10),
ok_quant integer
);
CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok
DO INSTEAD
UPDATE shoelace SET
sl_avail = sl_avail + NEW.ok_quant
WHERE sl_name = NEW.ok_name;
現在 Al 可以坐下來做這些事情直到(下面查詢的輸出)
al_bundy=> SELECT * FROM shoelace_arrive;
arr_name |arr_quant
----------+---------
sl3 | 10
sl6 | 20
sl8 | 20
(3 rows)
就是那些到貨列表中的東西.我們迅速的看一眼當前的數據,
al_bundy=> SELECT * FROM shoelace ORDER BY sl_name;
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 | 6|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)
把到貨鞋帶移到(shoelace_ok)中
al_bundy=> INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;
然后檢查結果
al_bundy=> SELECT * FROM shoelace ORDER BY sl_name;
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 | 6|brown | 60|cm | 60
sl4 | 8|black | 40|inch | 101.6
sl3 | 10|black | 35|inch | 88.9
sl8 | 21|brown | 40|inch | 101.6
sl5 | 4|brown | 1|m | 100
sl6 | 20|brown | 0.9|m | 90
(8 rows)
al_bundy=> SELECT * FROM shoelace_log;
sl_name |sl_avail|log_who|log_when
----------+--------+-------+--------------------------------
sl7 | 6|Al |Tue Oct 20 19:14:45 1998 MET DST
sl3 | 10|Al |Tue Oct 20 19:25:16 1998 MET DST
sl6 | 20|Al |Tue Oct 20 19:25:16 1998 MET DST
sl8 | 21|Al |Tue Oct 20 19:25:16 1998 MET DST
(4 rows)
從 INSERT ... SELECT 語句到這個結果經過了長長的一段過程.而且對它的描述將在本文檔的最后(但不是最后的例子:)首先是生成分析器輸出
INSERT INTO shoelace_ok SELECT
shoelace_arrive.arr_name, shoelace_arrive.arr_quant
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;
現在應用第一條規則 'shoelace_ok_ins' 把它轉換成
UPDATE shoelace SET
sl_avail = int4pl(shoelace.sl_avail, shoelace_arrive.arr_quant)
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
shoelace_ok *OLD*, shoelace_ok *NEW*,
shoelace shoelace
WHERE bpchareq(shoelace.sl_name, showlace_arrive.arr_name);
并且把原始的對 shoelace_ok 的 INSERT 丟棄掉.這樣重寫后的查詢再次傳入規則系統并且第二次應用了規則 'shoelace_upd' 生成
UPDATE shoelace_data SET
sl_name = shoelace.sl_name,
sl_avail = int4pl(shoelace.sl_avail, shoelace_arrive.arr_quant),
sl_color = shoelace.sl_color,
sl_len = shoelace.sl_len,
sl_unit = shoelace.sl_unit
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
shoelace_ok *OLD*, shoelace_ok *NEW*,
shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data showlace_data
WHERE bpchareq(shoelace.sl_name, showlace_arrive.arr_name)
AND bpchareq(shoelace_data.sl_name, shoelace.sl_name);
同樣這是一個 INSTEAD 規則并且前一個分析樹被丟棄掉.注意這個查詢仍然是使用視圖 shoelace ,但是規則系統還沒有完成(規則)循環,所以它繼續對(分析樹)應用規則 '_RETshoelace',然后我們得到
UPDATE shoelace_data SET
sl_name = s.sl_name,
sl_avail = int4pl(s.sl_avail, shoelace_arrive.arr_quant),
sl_color = s.sl_color,
sl_len = s.sl_len,
sl_unit = s.sl_unit
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
shoelace_ok *OLD*, shoelace_ok *NEW*,
shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data showlace_data,
shoelace *OLD*, shoelace *NEW*,
shoelace_data s, unit u
WHERE bpchareq(s.sl_name, showlace_arrive.arr_name)
AND bpchareq(shoelace_data.sl_name, s.sl_name);
同樣又是應用了一個更新規則并且我們繼續規則的附加,到了重寫的第三輪.這回應用規則 'log_shoelace' 生成下面分析樹
INSERT INTO shoelace_log SELECT
s.sl_name,
int4pl(s.sl_avail, shoelace_arrive.arr_quant),
getpgusername(),
datetime('now'::text)
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
shoelace_ok *OLD*, shoelace_ok *NEW*,
shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data showlace_data,
shoelace *OLD*, shoelace *NEW*,
shoelace_data s, unit u,
shoelace_data *OLD*, shoelace_data *NEW*
shoelace_log shoelace_log
WHERE bpchareq(s.sl_name, showlace_arrive.arr_name)
AND bpchareq(shoelace_data.sl_name, s.sl_name);
AND int4ne(int4pl(s.sl_avail, shoelace_arrive.arr_quant),
s.sl_avail);
在所有的規則都應用完后返回生成的分析樹.所以我們最終得到兩個等效于下面 SQL 語句的分析樹
INSERT INTO shoelace_log SELECT
s.sl_name,
s.sl_avail + shoelace_arrive.arr_quant,
getpgusername(),
'now'
FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data,
shoelace_data s
WHERE s.sl_name = shoelace_arrive.arr_name
AND shoelace_data.sl_name = s.sl_name
AND s.sl_avail + shoelace_arrive.arr_quant != s.sl_avail;
UPDATE shoelace_data SET
sl_avail = shoelace_data.sl_avail + shoelace_arrive.arr_quant
FROM shoelace_arrive shoelace_arrive,
shoelace_data shoelace_data,
shoelace_data s
WHERE s.sl_name = shoelace_arrive.sl_name
AND shoelace_data.sl_name = s.sl_name;
結果是從一個關系來的數據插入到另一個中,到了第三個中變成更新,在到第四個中變成更新加上記日志,最后在第五個規則中縮減為兩個查詢.
有一個小細節有點讓人難受.看看生成的查詢,shoelace_data 關系在可排列元素中出現了兩次而實際上絕對可以縮為一次.因為優化器不處理這些,所以對規則系統輸出的 INSERT 的執行規劃會是
Nested Loop
-> Merge Join
-> Seq Scan
-> Sort
-> Seq Scan on s
-> Seq Scan
-> Sort
-> Seq Scan on shoelace_arrive
-> Seq Scan on shoelace_data
在省略多余的可排列元素后的結果將是
Merge Join
-> Seq Scan
-> Sort
-> Seq Scan on s
-> Seq Scan
-> Sort
-> Seq Scan on shoelace_arrive
這也會在日志關系中生成完全一樣的記錄.因此,規則系統導致對 shoelace_data 關系的一次多余的掃描,而且同樣多余的掃描會在 UPDATE 里也一樣多做一次.不過要想把這些不足去掉是一樣太困難的活了.
Postgres 規則系統及其功能的最后一個演示.有個金發美女出售鞋帶.而且 Al 可能永遠不知道的是,她不僅漂亮,而且聰明 -有點太聰明了.因此,時不時的會發生 Al 訂購的鞋帶完全不能銷售的情況.這回他定了1000對洋紅色的鞋帶并且因為其他類型的目前還沒貨所以他忘了買一些,他還準備在他的數據庫里增加一些粉紅的鞋帶.
al_bundy=> INSERT INTO shoelace VALUES
al_bundy-> ('sl9', 0, 'pink', 35.0, 'inch', 0.0);
al_bundy=> INSERT INTO shoelace VALUES
al_bundy-> ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
因為常發生這種事,我們必須看一眼鞋帶記錄表,看看有沒有那些某一時段沒有相配的鞋子的(鞋帶).我們可以在每次都用一個復雜的語句實現這些,或者我們可以創建一個用于這個方面的視圖.如下
CREATE VIEW shoelace_obsolete AS
SELECT * FROM shoelace WHERE NOT EXISTS
(SELECT shoename FROM shoe WHERE slcolor = sl_color);
它的輸出是
al_bundy=> SELECT * FROM shoelace_obsolete;
sl_name |sl_avail|sl_color |sl_len|sl_unit |sl_len_cm
----------+--------+----------+------+--------+---------
sl9 | 0|pink | 35|inch | 88.9
sl10 | 1000|magenta | 40|inch | 101.6
那 1000 條洋紅色鞋帶,在把它們仍掉之前我們必須先欠著 Al,不過那是另一回事.粉紅的記錄我們要刪掉.為了讓這事對 Postgres 有點難度,我們不直接刪除它們.取而代之的是我們再創建一個視圖
CREATE VIEW shoelace_candelete AS
SELECT * FROM shoelace_obsolete WHERE sl_avail = 0;
然后用下面方法做:
DELETE FROM shoelace WHERE EXISTS
(SELECT * FROM shoelace_candelete
WHERE sl_name = shoelace.sl_name);
所以:
al_bundy=> 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 | 6|brown | 60|cm | 60
sl4 | 8|black | 40|inch | 101.6
sl3 | 10|black | 35|inch | 88.9
sl8 | 21|brown | 40|inch | 101.6
sl10 | 1000|magenta | 40|inch | 101.6
sl5 | 4|brown | 1|m | 100
sl6 | 20|brown | 0.9|m | 90
(9 rows)
對一個視圖的 DELETE,這個視圖帶有一個總共使用了四個獨立/聯合的視圖的子查詢資格(條件),這四個視圖之一本身有一個擁有對一個視圖的子查詢資格(條件),該條件計算使用的視圖的列;最后重寫成了一個分析樹,該分析樹從一個真正的表里面把需要刪除的數據刪除.
我想在現實世界里只有很少的機會需要做上面的這類事情.但這些東西能工作讓我很開心.
真相是:我在寫本文檔時做上面的試驗又發現了一個錯誤(bug).但在去除該錯誤之后我有點驚奇的發現這些都正常工作了.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
規則和權限
由于 Postgres 規則系統對查詢的重寫,非初始查詢指定的其他表/視圖被訪問.使用更新規則,這可能包括對表的寫權限.
重寫規則并不擁有一個獨立的所有者.關系(表或視圖)的所有者自動成為重寫規則的缺省所有者.Postgres 規則系統改變缺省的訪問控制系統的特性.因規則而使用的關系在(規則)重寫時要對定義規則的關系所有者的權限進行檢查.這意味著一個用戶只需要對他的查詢里指定的表/視圖擁有所需的權限就可進行操作.
例如:某用戶有一個電話號碼列表,其中一些是私人的,另外的一些是辦公室秘書需要的.他可以用下面方法構建(查詢):
CREATE TABLE phone_data (person text, phone text, private bool);
CREATE VIEW phone_number AS
SELECT person, phone FROM phone_data WHERE NOT private;
GRANT SELECT ON phone_number TO secretary;
除了他以外(還有數據庫超級用戶)沒有人可以訪問 phone_data 表.但因為 GRANT,秘書可以從 phone_number 視圖里進行 SELECT.規則系統將把從 phone_number 里的 SELECT 重寫為從 phone_data 里的 SELECT 并將增加資格(條件).只有私人條件為假的記錄才可以選出.因為用戶是 phone_number 的所有者,他對 phone_data 的讀訪問的權限現在要進行檢查,而這個查詢是被賦予的.所以對訪問 phone_number 的權限檢查仍然要進行,所以除了秘書外沒有人可以使用它.
權限檢查是按規則逐條進行的.所以此時的秘書是唯一的一個可以看到公共電話號碼的人.但秘書可以設立另一個視圖并且賦予該視圖公共權限.這樣,任何人都可以通過秘書的視圖看到 phone_number 數據.秘書不能做的事情是創建一個直接訪問 phone_data 的視圖(實際上他是可以的,但沒有任何作用,因為每個訪問都會因通不過權限檢查而被踢出事務).而且用戶很快會認識到,秘書開放了他的 phone_number 視圖后,他還可以 REVOKE (撤回)他的訪問權限.這樣,所有對秘書視圖的訪問馬上就失效了.
有些人會認為這種逐條規則的檢查是一個安全漏洞,但事實上不是.如果這樣做不能奏效,秘書將必須建立一個與 phone_number 有相同字段的表并且每天一次的拷貝數據進去.那么這是他自己的數據因而他可以賦予他允許的任何人訪問的權力.一個 GRANT 意味著 "我信任你".如果某個你信任的人做了上面的事情,那你就該想想是否該 REVOKE 了.
這個機制同樣可以用于更新規則.在上一章的例子里,Al 的數據庫里的表的所有者可以把(賦予)鞋帶視圖 GRANT SELECT,INSERT,UPDATE 和 DELETE 給 al.但對 shoelace_log 只有SELECT 權限.寫日志記錄的規則動作(action)仍然可以成功的執行.并且 Al 可以看到日志記錄.但他不能創建偽記錄,而且他也不能對現有記錄進行修改或刪除.
警告:GRANT ALL 目前包括 RULE (規則)權限.這意味著賦予權限的用戶可以刪除規則,做修改和重新安裝之.我想這些可能很快就會改變.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
規則與觸發器之比較
許多用觸發器可以干的事情同樣也可以用 Postgres 規則系統來完成.目前不能用規則來實現的東西是某些約束.我們還可能在某字段的值沒有在另一個表里出現的情況下用一條合格的規則把查詢重寫為 NOTHING.不過這樣做數據就會被不聲不響的被仍掉,因而這也不是一個好主意.如果需要檢查有效的值,而且如果是無效值出現時要生成一個錯誤信息,這種情況下現在我們要用觸發器來做.
另一方面,一個用于 INSERT 一個視圖的觸發器可以做到與規則一樣,把數據放到另外的地方去而取代對視圖的插入.但它不能在 UPDATE 或 DELETE 時做同樣的事情,因為在視圖關系里沒有可供掃描的真實數據因而觸發器將永遠不被調用.這時只有規則可用.
對于兩者都可用的情況,哪個更好取決于對數據庫的使用.觸發器用于任何涉及到一次的行.規則修改分析樹或生成另外一個.所以如果在一個語句中涉及到多行,一個生成一個額外查詢的規則通常可能會比一個對每一行都分別執行一次的觸發器要好一些.
例如:這里有兩個表
CREATE TABLE computer (
hostname text -- indexed
manufacturer text -- indexed
);
CREATE TABLE software (
software text, -- indexed
hostname text -- indexed
);
兩個表都有好幾千行,并且 hostname 是唯一的.hostname 字段包含計算機完全合格的域名.規則/觸發器應該對來自軟件的刪除已刪除主機的行的動作進行約束.因為觸發器在每個獨立的行刪除的時候都要調用,它可以使用下面語句
DELETE FROM software WHERE hostname = $1;
寫在一個準備好了并且保存了的規劃里,把 hostname (主機名)作為參數傳遞.規則應該這樣寫
CREATE RULE computer_del AS ON DELETE TO computer
DO DELETE FROM software WHERE hostname = OLD.hostname;
現在我們看看這兩種不同的刪除.在下面情況
DELETE FROM computer WHERE hostname = 'mypc.local.net';
對表 computer 使用索引(快速)進行掃描并且由觸發器聲明的查詢也用索引進行掃描(同樣快速).規則里多出來的查詢是一個
DELETE FROM software WHERE computer.hostname = 'mypc.local.net'
AND software.hostname = computer.hostname;
因為已經建立了合適的索引,優化器將創建一個下面的規劃
Nestloop
-> Index Scan using comp_hostidx on computer
-> Index Scan using soft_hostidx on software
所以在規則和觸發器的實現之間沒有太多的速度差別.下面的刪除我們希望刪掉所有 2000 個以 'old' 開頭的計算機.有兩個可能的用于這個用途的查詢.一個是
DELETE FROM computer WHERE hostname >= 'old'
AND hostname < 'ole'
這樣的規則查詢的規劃將會是
Hash Join
-> Seq Scan on software
-> Hash
-> Index Scan using comp_hostidx on computer
另一個可能的查詢是
DELETE FROM computer WHERE hostname ~ '^old';
它的執行規劃是
Nestloop
-> Index Scan using comp_hostidx on computer
-> Index Scan using soft_hostidx on software
這表明,優化器不能認識到表 computer 的 hostname (計算機主機名)的資格(條件)在多個資格表達式以 AND (與)的方式組合在一起時同樣可以用于 software (軟件),就象在用規則表達式的查詢里一樣.觸發器將在任何 2000 個要被刪除的舊計算機里被調用一次,結果是對 computer 的一次索引掃描和對 software 的2000次索引掃描.規則的實現將在兩個對索引的查詢實現之.所以這是由 software 表的實際大小決定規則進行了順序掃描后是否還是快一些.2000 個在 SPI 管理器上的查詢的執行是要點時間的,即使所有要使用的索引塊都很快在緩沖里出現.
我們看看最后一個查詢
DELETE FROM computer WHERE manufacurer = 'bim';
同樣,這也會導致從 computer 表里的多行刪除.所以觸發器同樣會向執行器提交很多查詢.但規則規劃又將是對兩個 IndexScan (索引掃描)的 Nestloop (內部循環).只對 computer 用另外一個索引:
Nestloop
-> Index Scan using comp_manufidx on computer
-> Index Scan using soft_hostidx on software
從規則查詢出來的東西是
DELETE FROM software WHERE computer.manufacurer = 'bim'
AND software.hostname = computer.hostname;
在任何一個情況下,從規則系統出來的額外查詢都或多或少與查詢中涉及到的行的數量相對獨立.
另一情況是(更新)UPDATE,這時某字段的更改決定一個規則動作(action)是否被執行.在 Postgres 版本 6.4 里,對規則事件的字段/屬性聲明被取消了(將在 6.5 晚些版本,也可能早些的版本中恢復-耐心點).所以現在創建象在 showlace_log 里那樣的規則的唯一的辦法是用一個規則資格(條件)來做.結果是引入了一個永遠執行的額外的查詢,即使我們關心的字段/屬性因為沒有在初始的查詢的目標列表里出現而不能修改也是這樣.當這個特性(字段屬性聲明)重新可用后,這將是規則優于觸發器的又一個方面.在這種情況下的觸發器的定義必然會無法優化,因為觸發器的動作只有在聲明的字段的更新被隱含在觸發器的功能里面時才能執行.對觸發器的執行只允許到行的級別,所以當涉及到行時,觸發器就會按定義被觸發進行動作.而規則系統將通過對目標列表的掃描獲知(是否動作)并且在字段/屬性沒有被涉及到時把多余的查詢完全去掉.所以不管規則合格與否,如果有可能有些事情要做的話,都將進行掃描.
規則只是在它們的動作(action)生成了又大又爛的資格(條件)聯合時才比觸發器有較大速度差異,這時優化器將失效.規則是大榔頭.大榔頭的不慎使用會導致大破壞.但合理的用勁,它們可以釘上任何釘子.
--------------------------------------------------------------------------------