【IT168 服务器学院】视图和规则系统
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 系统表里的视图的信息与一般表的信息完全一样。 所以对于查询分析器来说,表和视图之间完全没有区别。 它们是同样的事物:关系。
SELECT规则如何运转
ON SELECT 的规则在最后一步应用于所有查询, 哪怕给出的命令是一条INSERT,UPDATE或DELETE。 而且与其他(规则)有不同的语意, 那就是它们在现场修改查询树而不是创建一个新的(查询树)。 所以我们先介绍SELECT的规则。
目前,一个 ON SELECT 规则里只能有一个动作(action), 而且它必须是一个无条件的 INSTEAD (取代)的 SELECT 动作。 有这个限制是为了令规则安全到普通用户也可以打开它们, 并且它限制 ON SELECT 规则使之行为类似试图。
本文档的例子是两个连接视图, 它们做一些运算并且因此会涉及到更多视图的使用。 这两个视图之一稍后将利用对INSERT, UPDATE 和 DELETE 操作附加规则的方法客户化, 这样做最终的结果就会是这个视图表现得象一个具有一些特殊功能的真正的表。 这个例子可不是适合于开始的简单易懂的例子, 从这个例子开始讲可能会让我们的讲解变得有些难以理解。 但是我们认为用一个覆盖所有关键点的例子来一步一步讨论要比举很多例子搞乱思维好多了。
比如,我们需要一个小巧的 min 函数用于返回两个整数值中的小的那个。 我们用下面方法创建它
CREATE FUNCTION min(integer, integer) RETURNS integer AS $$
SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END
$$ LANGUAGE SQL STRICT;
我们头两个规则系统要用到的真实的表的描述如下:
CREATE TABLE shoe_data (
shoename text, -- 主键
sh_avail integer, -- (鞋的)可用对数
slcolor text, -- 首选的鞋带颜色
slminlen real, -- 鞋带最短长度
slmaxlen real, -- 鞋带最长长度
slunit text -- 长度单位
);
CREATE TABLE shoelace_data (
sl_name text, -- 主键
sl_avail integer, -- (鞋带的)可用双数
sl_color text, -- 鞋带颜色
sl_len real, -- 鞋带长度
sl_unit text -- 长度单位
);
CREATE TABLE unit (
un_name text, -- 主键
un_fact real -- 转换成厘米的系数
);
你可以看到,这些表代表鞋店的数据。
视图创建为
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命令(也是我们用到的最简单的一个) 将创建一个关系 shoelace 并且在 pg_rewrite 表里增加一个记录,告诉系统有一个重写规则应用于所有范围表里引用了 shoelace 关系的查询。该规则没有规则条件(将在非 SELECT 规则讨论,因为目前的 SELECT 规则不可能有这些东西)并且它是 INSTEAD(取代)型的。 要注意规则条件与查询条件不一样。我们的规则动作(action)有一个查询条件。 规则的动作是一个查询树,这个查询是树视图创建命令中的 SELECT 语句的一个拷贝。
注意: 你在表 pg_rewrite 里看到的两个额外的用于 NEW 和 OLD 的范围表记录(因历史原因, 在打印出来的查询树里叫 *NEW* 和 *CURRENT* ) 对 SELECT 规则不感兴趣。
现在我们填充 unit,shoe_data 和 shoelace_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;
然后把这些交给规则系统。规则系统把范围表(rangetable)过滤一遍, 检查一下在有没有适用任何关系的任意规则。当为 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 *OLD*,shoelace *NEW*。这些记录并不直接参与查询, 因为它们没有被子查询的连接树或者目标列表引用。 重写器用它们存储最初出现在引用视图的范围表里面的访问权限检查。 这样,执行器仍然会检查该用户是否有访问视图的合适权限, 即使在重写查询里面没有对视图的直接使用也如此。
这是应用的第一个规则。规则系统继续检查顶层查询里剩下的范围表记录(本例中没有了), 并且它在加进来的子查询中递归地检查范围表记录,看看其中有没有引用视图的。 (不过这样不会扩展 *OLD* 或 *NEW* — 否则我们会无穷递归下去!) 在这个例子中,没有用于 shoelace_data 或 unit 的重写规则, 所以重写结束并且上面的就是给规划器的最终结果。
现在我们想写这么一个查询:这个查询找出目前在店里有配对鞋带的鞋子,并且配对的鞋带数大于或等于二。
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,
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 的规则替换到子查询范围表里, 生成一个最终的三层查询树:
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;
最后规划器会把这个树压缩成一个两层查询树: 最下层的 SELECT 将"拖到"中间的 SELECT 中, 因为没有必要分别处理它们。但是中间的 SELECT 仍然和顶层的分开,因为它包含聚集函数。如果我们把它们也拉进来, 那它就会修改最顶层的 SELECT 的行为,那可不是我们想要的。 不过,压缩查询树是重写系统自己不需要关心的优化操作。
注意: 目前规则系统中没有用于视图规则递归终止机制(只有用于其他规则的)。 这一点不会造成太大的损害, 因为把这个(规则)无限循环(不断膨胀后端,直到耗尽内存)的唯一方法是创建表然后后手工用CREATE RULE命令创建视图规则, 这个规则是这样的:一个从其他地方来的选择(select)选择(select)了这个视图。 如果使用了CREATE VIEW,这一点是永远不会发生的, 因为第二个关系不存在,所以第一个CREATE VIEW 不能从第二个视图里面选择(select)。