第四十二章. 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)生成了又大又烂的资格(条件)联合时才比触发器有较大速度差异,这时优化器将失效.规则是大榔头.大榔头的不慎使用会导致大破坏.但合理的用劲,它们可以钉上任何钉子.
--------------------------------------------------------------------------------