PostgreSQL 规则系统的工作机制及查询树解析

0 阅读10分钟

规则系统的工作机制及查询树解析

一、规则系统的核心定位与工作流程

要深入理解规则系统的运行逻辑,核心在于明确其调用时机、输入参数及输出结果。规则系统在数据库架构中处于解析器与规划器之间,承担着查询重写的关键职责,其工作流程具有明确的输入输出边界。

规则系统的输入包含两部分:

  1. 解析器输出的查询树;
  2. 用户定义的重写规则(本质上也是查询树,仅附加少量额外描述信息)。

其输出为零个或多个查询树,这一特性使得规则系统的输入输出与规划器的处理对象完全兼容——所有经过规则系统的内容,均可转化为对应的SQL语句,确保了整个数据库执行链路的连贯性。

二、查询树的定义与呈现方式

查询树是SQL语句的内部表示形式,SQL语句的每一个组成部分都会被独立存储在查询树中,形成结构化的层级关系。

2.1 查询树的查看方式

通过配置数据库参数,可在服务器日志中查看查询树的具体内容:设置debug_print_parsedebug_print_rewrittendebug_print_plan参数后,解析阶段、重写阶段及规划阶段的查询树将分别输出至日志。

2.2 查询树的存储形式

规则动作对应的查询树存储于系统目录pg_rewrite中,其存储格式虽与日志输出的格式化形式不同,但包含的信息完全一致。

说明:直接阅读原始查询树需具备一定的实践经验,而查询树的SQL表示形式已足够支撑对规则系统的理解,本文不涉及原始查询树的阅读方法,仅聚焦于其SQL表现形式及核心组成部分。

三、查询树的核心组成部分

3.1 命令类型

命令类型是一个基础标识值,用于明确生成该查询树的SQL命令类型,具体包括SELECTINSERTUPDATEDELETE四类,直接决定了查询树的处理方向和后续执行逻辑。

3.2 范围表

范围表是查询中涉及的所有关系(表、视图等)的集合列表。对于SELECT语句,范围表对应于FROM关键字后指定的所有关系。

每个范围表项均包含两层核心信息:

  1. 标识对应的表或视图;
  2. 定义该关系在查询其他部分中的引用名称。

在查询树内部,范围表项通过编号而非名称进行引用,这一设计可有效避免SQL语句中出现重复名称导致的冲突(此类冲突可能在规则范围表合并后产生),确保了查询树对复杂命名场景的兼容性。

3.3 结果关系

结果关系是指向范围表的索引,用于指定查询结果的存储或作用目标关系。不同类型的SQL命令对结果关系的需求存在差异:

  • SELECT查询无结果关系(特殊场景SELECT INTO本质等价于CREATE TABLEINSERT ... SELECT的组合,本文不单独讨论);
  • INSERTUPDATEDELETE命令的结果关系为待修改的表或视图,是命令执行的核心目标对象。

3.4 目标列表

目标列表是一组表达式的集合,用于定义查询的输出结果,其构成和作用随命令类型变化而不同:

命令类型目标列表的核心作用补充说明
SELECT对应SELECTFROM之间的表达式(解析器会将通配符*扩展为具体列名,规则系统不直接处理*),构建查询最终输出-
DELETE无需目标列表(无输出结果);规划器自动添加CTID项(普通表)或整行变量(视图),用于定位待删除行CTID为系统列,存储行物理位置
INSERT描述待插入的新行数据,由VALUES子句或INSERT ... SELECT中的SELECT子句表达式构成重写阶段补充有默认值的未赋值列,规划阶段填充无值无默认值列的空值
UPDATE描述替换旧行的新行数据,仅包含SET column = expression中的表达式规划阶段补充缺失列(复制旧行值),并添加CTID/整行变量定位旧行

目标列表中每个表达式的构成形式灵活,可是常量值、范围表关系的列变量、参数,也可是由函数调用、常量、变量、操作符组合形成的表达式树。

3.5 条件

查询条件是一个布尔型表达式,其构成逻辑与目标列表表达式一致,核心作用是判断是否对最终结果行执行对应操作(SELECT/INSERT/UPDATE/DELETE),对应SQL语句中的WHERE子句。

条件表达式的结果直接决定了行级操作的执行与否,是查询过滤的核心依据。

3.6 连接树

连接树用于描述FROM子句的结构,适配不同复杂度的查询场景:

  • 简单查询(如SELECT ... FROM a, b, c):连接树为FROM项的无序列表,允许按任意顺序执行连接操作;
  • JOIN表达式的查询(尤其是外连接):连接树需严格遵循JOIN的显式顺序,呈现层级结构。

与特定JOIN子句(ON/USING)关联的约束条件,会作为附加条件表达式存储在对应连接树节点中。顶层WHERE表达式也会被存储为顶层连接树项的附加条件,因此连接树本质上整合了SELECT语句的FROM子句与WHERE子句的核心逻辑。

3.7 其他部分

查询树还包含ORDER BY子句等其他组成部分,此类内容虽会在规则系统应用过程中被部分替换,但与规则系统的核心工作机制关联较弱,本文不再展开。

四、PostgreSQL视图与规则系统的实践应用

4.1 SELECT规则的工作机制

ON SELECT规则是所有查询的最后一步应用规则,无论原始命令类型,且会就地修改查询树而非新建(与其他规则语义不同)。

目前ON SELECT规则仅支持单个无条件INSTEAD SELECT动作,该限制保障了规则安全性,使其行为贴合视图特性。

4.1.1 基础数据表创建

示例基于鞋店数据场景,需创建3张基础表(存储鞋子、鞋带及单位转换信息),创建语句如下:

-- 鞋子信息表
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           -- 转换到厘米的参数
);
4.1.2 视图创建

基于基础表创建3个嵌套视图,实现数据关联与单位转换,语句如下:

-- 鞋子视图(单位转换)
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会同步创建视图关系及pg_rewrite目录项(记录重写规则,引用视图时触发);该规则为无条件INSTEAD规则,动作对应视图创建时的SELECT查询树;pg_rewrite项中的NEW/OLD范围表项对SELECT规则无影响。

4.1.3 数据插入与查询演示

插入测试数据后,查询视图验证规则作用:

-- 插入单位转换数据
INSERT INTO unit VALUES ('cm', 1.0), ('m', 100.0), ('inch', 2.54);

-- 插入鞋子数据
INSERT INTO shoe_data VALUES 
('sh1', 2, 'black', 70.0, 90.0, 'cm'),
('sh2', 0, 'black', 30.0, 40.0, 'inch'),
('sh3', 4, 'brown', 50.0, 65.0, 'cm'),
('sh4', 3, 'brown', 40.0, 50.0, 'inch');

-- 插入鞋带数据
INSERT INTO shoelace_data VALUES 
('sl1', 5, 'black', 80.0, 'cm'),
('sl2', 6, 'black', 100.0, 'cm'),
('sl3', 0, 'black', 35.0 , 'inch'),
('sl4', 8, 'black', 40.0 , 'inch'),
('sl5', 4, 'brown', 1.0 , 'm'),
('sl6', 0, 'brown', 0.9 , 'm'),
('sl7', 7, 'brown', 60 , 'cm'),
('sl8', 1, 'brown', 40 , 'inch');

-- 查询shoelace视图
SELECT * FROM shoelace;

查询结果(标准化表格展示):

sl_namesl_availsl_colorsl_lensl_unitsl_len_cm
sl15black80cm80.0
sl26black100cm100.0
sl30black35inch88.9
sl48black40inch101.6
sl54brown1.0m100.0
sl60brown0.9m90.0
sl77brown60cm60.0
sl81brown40inch101.6
4.1.4 SELECT规则的作用过程

SELECT * FROM shoelace为例,规则系统的处理流程如下:

  1. 解析生成原始查询树:解析器将语句解析为含目标列与shoelace视图范围表的查询树:

    1. SELECT shoelace.sl_name, shoelace.sl_avail,
             shoelace.sl_color, shoelace.sl_len,
             shoelace.sl_unit, shoelace.sl_len_cm
        FROM shoelace shoelace;
      
  2. 匹配重写规则:规则系统遍历范围表,匹配到shoelace视图的_RETURN规则(动作对应视图创建时的SELECT查询树);

  3. 重写查询树:重写器用规则动作的子查询替换原始视图引用,重写后:

    1. 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;
      
    2.   注:子查询范围表含shoelace old/new项,用于存储视图访问权限信息,确保执行器验证权限。
  4. 递归检查:规则系统递归检查顶层及子查询范围表,确认无其他视图引用后,将最终查询树提交给规划器。

4.1.5 多视图嵌套查询的规则应用

查询嵌套视图(如shoe_ready)时,规则系统会递归应用重写规则。例如执行:

SELECT * FROM shoe_ready WHERE total_avail >= 2;
4.1.5.1 子查询提升示例

规则重写后生成三层嵌套SQL(层级清晰但执行效率低),规划器通过“子查询提升”优化为单层SQL(便于优化连接路径):

提升前(三层嵌套SQL)

SELECT shoe_ready.shoename,
       shoe_ready.sh_avail,
       shoe_ready.sl_name,
       shoe_ready.sl_avail,
       shoe_ready.total_avail
  FROM (-- shoe_ready视图子查询
        SELECT rsh.shoename,
               rsh.sh_avail,
               rsl.sl_name,
               rsl.sl_avail,
               least(rsh.sh_avail, rsl.sl_avail) AS total_avail
          FROM (-- shoe视图子查询
                SELECT sh.shoename,
                       sh.sh_avail,
                       sh.slcolor,
                       sh.slminlen * un.un_fact AS slminlen_cm,
                       sh.slmaxlen * un.un_fact AS slmaxlen_cm
                  FROM shoe_data sh, unit un
                 WHERE sh.slunit = un.un_name) rsh,
               (-- shoelace视图子查询
                SELECT s.sl_name,
                       s.sl_avail,
                       s.sl_color,
                       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;

提升后(单层合并SQL)

SELECT sh.shoename,
       sh.sh_avail,
       s.sl_name,
       s.sl_avail,
       least(sh.sh_avail, s.sl_avail) AS total_avail
  FROM shoe_data sh, unit un1, shoelace_data s, unit un2
 WHERE sh.slunit = un1.un_name  -- shoe视图关联条件
   AND s.sl_unit = un2.un_name  -- shoelace视图关联条件
   AND s.sl_color = sh.slcolor  -- shoe_ready视图关联条件
   AND (s.sl_len * un2.un_fact) >= (sh.slminlen * un1.un_fact)
   AND (s.sl_len * un2.un_fact) <= (sh.slmaxlen * un1.un_fact)
   AND least(sh.sh_avail, s.sl_avail) >= 2;  -- 顶层过滤条件
4.1.5.2 子查询提升的核心逻辑
  1. 表合并:提取各层基础表至顶层FROM,用别名(如un1/un2)区分重复表引用,避免冲突;
  2. 条件整合:合并各层关联条件、过滤条件至顶层WHERE,形成完整逻辑;
  3. 表达式简化:直接代入子查询计算表达式,消除冗余别名映射。

该优化使规划器可全局评估表连接顺序与过滤优先级,避免嵌套导致的局部优化局限,生成高效执行计划。

4.2 非SELECT语句中的视图规则

SELECT与非SELECT命令(INSERT/UPDATE/DELETE)的查询树核心结构一致,仅命令类型结果关系存在差异——非SELECT命令的结果关系指向目标关系项。

4.2.1 示例对比

以表t1(a, b)t2(a, b)为例,两条语句的查询树结构高度相似:

-- SELECT语句
SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a;

-- UPDATE语句
UPDATE t1 SET b = t2.b FROM t2 WHERE t1.a = t2.a;

规划器为UPDATE补充缺失列(复制旧行值)及CTID项,用于定位待更新行,等效于:

SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
4.2.2 执行器处理逻辑

执行器通过CTID定位旧行,插入新行后标记旧行为失效,事务提交后由清理器移除(这是PostgreSQL快速回滚的核心原因)。

规则系统对非SELECT命令的视图处理逻辑,与SELECT完全一致。