PostgreSQL的学习心得和知识总结(一百五十)|[performance]更好地处理冗余 IS [NOT] NULL 限定符


注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
7、参考书籍:《事务处理 概念与技术》
8、Magic Tricks for Postgres psql: Settings, Presets, Echo, and Saved Queries,点击前往


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL master源码开发而成


更好地处理冗余 IS [NOT] NULL 限定符

  • 文章快速说明索引
  • 功能使用背景说明
  • 功能实现使用说明
  • 功能实现源码解析
    • 简单情况的处理
      • 永真永假的处理
      • or语句的处理
    • 连接情况的处理
      • 是否可忽略
      • 是否可简化
      • or语句的处理
    • 父子继承的处理



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、更好地处理冗余 IS [NOT] NULL 限定符


学习时间:

2024年08月01日 22:16:59


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Centos8+PostgreSQL master +Oracle19C+MySQL8.0

postgres=# select version();version                                                   
------------------------------------------------------------------------------------------------------------PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
(1 row)postgres=##-----------------------------------------------------------------------------#SQL> select * from v$version;          BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0#-----------------------------------------------------------------------------#mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)mysql>

功能使用背景说明

patch1,如下:

SHA-1: b262ad440edecda0b1aba81d967ab560a83acb8a* Add better handling of redundant IS [NOT] NULL quals

Until now PostgreSQL has not been very smart about optimizing away IS NOT NULL base quals on columns defined as NOT NULL. The evaluation of these needless quals adds overhead. Ordinarily, anyone who came complaining about that would likely just have been told to not include the qual in their query if it’s not required. However, a recent bug report indicates this might not always be possible.
Bug 17540 highlighted that when we optimize Min/Max aggregates the IS NOT NULL qual that the planner adds to make the rewritten plan ignore NULLs can cause issues with poor index choice. That particular case demonstrated that other quals, especially ones where no statistics are available to allow the planner a chance at estimating an approximate selectivity for can result in poor index choice due to cheap startup paths being prefered with LIMIT 1.
Here we take generic approach to fixing this by having the planner check for NOT NULL columns and just have the planner remove these quals (when they’re not needed) for all queries, not just when optimizing Min/Max aggregates.
Additionally, here we also detect IS NULL quals on a NOT NULL column and transform that into a gating qual so that we don’t have to perform the scan at all. This also works for join relations when the Var is not nullable by any outer join.
This also helps with the self-join removal work as it must replace strict join quals with IS NOT NULL quals to ensure equivalence with the original query.


  • 到目前为止,PostgreSQL 在优化定义为 NOT NULL 的列上的 IS NOT NULL 基本限定符方面还不是很聪明。评估这些不必要的限定符会增加开销。通常,任何抱怨这一点的人都可能被告知如果不需要,不要在查询中包含限定符。然而,最近的错误报告表明这可能并不总是可行的。

  • Bug 17540 强调,当我们优化 Min/Max 聚合时,规划器添加的 IS NOT NULL 限定符会使重写计划忽略 NULL,这可能会导致索引选择不当的问题。该特定案例表明,其他限定条件,尤其是没有可用统计数据让规划器有机会估计近似选择性的限定条件,可能会导致索引选择不佳,因为 LIMIT 1 优先选择廉价的启动路径。

  • 在这里,我们采用通用方法解决这个问题,让规划器检查 NOT NULL 列,并让规划器在所有查询中删除这些限定条件(当它们不需要时),而不仅仅是在优化最小/最大聚合时。

  • 此外,这里我们还检测 NOT NULL 列上的 IS NULL 限定条件并将其转换为门控限定条件,这样我们就不必执行扫描了。当 Var 不能被任何外连接为空时,这也适用于连接关系。

  • 这也有助于自连接删除工作,因为它必须用 IS NOT NULL 限定条件替换严格连接限定条件,以确保与原始查询等效。


功能实现使用说明

首先看一下使用案例,案例一如下(测试 始终为真 的限制是否被忽略,始终为假 的限制是否被常量 FALSE 替换):

postgres=# select version();version                                                   
------------------------------------------------------------------------------------------------------------PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-15), 64-bit
(1 row)-- 目前我们只检查 NullTest 限定词和包含 NullTest 限定词的 OR 子句。我们可能会在未来扩展它
postgres=# CREATE TABLE pred_tab (a int NOT NULL, b int, c int NOT NULL);
CREATE TABLE-- 全文用例1
-- 测试限制条款
-- 确保当列不可为空时忽略 IS_NOT_NULL 限定符
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.a IS NOT NULL;QUERY PLAN       
------------------------Seq Scan on pred_tab t
(1 row)-- 全文用例2
-- 确保可空列上的 IS_NOT_NULL 限定符不会被忽略
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.b IS NOT NULL;QUERY PLAN         
---------------------------Seq Scan on pred_tab tFilter: (b IS NOT NULL)
(2 rows)-- 全文用例3
-- 确保对于不可为空的列,IS_NULL 限定符裁剪为常量 FALSE
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.a IS NULL;QUERY PLAN        
--------------------------ResultOne-Time Filter: false
(2 rows)-- 全文用例4
-- 确保在可空列上 IS_NULL 限定符不会降低为常量 FALSE
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.b IS NULL;QUERY PLAN       
------------------------Seq Scan on pred_tab tFilter: (b IS NULL)
(2 rows)postgres=#

案例二如下(限制条款中的 OR 条款测试):

-- 全文用例5
-- 确保当 OR 分支始终为真时,OR 子句被忽略
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.a IS NOT NULL OR t.b = 1;QUERY PLAN       
------------------------Seq Scan on pred_tab t
(1 row)-- 全文用例6
-- 确保对于无法证明始终正确的 NullTest,不会忽略 OR 子句
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.b IS NOT NULL OR t.a = 1;QUERY PLAN               
----------------------------------------Seq Scan on pred_tab tFilter: ((b IS NOT NULL) OR (a = 1))
(2 rows)-- 全文用例7
-- 确保当所有分支均可证明为假时,OR 子句简化为常数 FALSE
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.a IS NULL OR t.c IS NULL;QUERY PLAN        
--------------------------ResultOne-Time Filter: false
(2 rows)-- 全文用例8
-- 确保当并非所有分支都可证明为假时,OR 子句不会简化为常量 FALSE
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t WHERE t.b IS NULL OR t.c IS NULL;QUERY PLAN               
----------------------------------------Seq Scan on pred_tab tFilter: ((b IS NULL) OR (c IS NULL))
(2 rows)postgres=#

备注:上面案例(最下面这个)是不是优化到 Filter: (b IS NULL)会更好?因为c IS NULL永假!如下:

postgres=# EXPLAIN (COSTS OFF)
SELECT * FROM pred_tab t WHERE t.b IS NULL and t.c IS NULL;QUERY PLAN        
--------------------------ResultOne-Time Filter: false
(2 rows)postgres=#

案例三如下(测试连接子句):

-- 全文用例9
-- 确保忽略 IS_NOT_NULL 限定符,因为 a) 它位于 NOT NULL 列上,并且 b) 其 Var 不能通过任何外连接为空
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON TRUE
postgres-#     LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL;QUERY PLAN                    
-------------------------------------------------Nested Loop Left Join->  Seq Scan on pred_tab t1->  Materialize->  Nested Loop Left Join->  Seq Scan on pred_tab t2->  Materialize->  Seq Scan on pred_tab t3
(7 rows)-- 全文用例10
-- 确保当外连接将列设为可空时,不会忽略 IS_NOT_NULL 限定符
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON t1.a = 1
postgres-#     LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL;QUERY PLAN                 
-------------------------------------------Nested Loop Left JoinJoin Filter: (t2.a IS NOT NULL)->  Nested Loop Left JoinJoin Filter: (t1.a = 1)->  Seq Scan on pred_tab t1->  Materialize->  Seq Scan on pred_tab t2->  Materialize->  Seq Scan on pred_tab t3
(9 rows)-- 全文用例11
-- 确保 IS_NULL 限定符被简化为常量 FALSE,因为 a) 它位于 NOT NULL 列上,并且 b) 它的 Var 不能通过任何外连接为空
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON TRUE
postgres-#     LEFT JOIN pred_tab t3 ON t2.a IS NULL AND t2.b = 1;QUERY PLAN                     
---------------------------------------------------Nested Loop Left Join->  Seq Scan on pred_tab t1->  Materialize->  Nested Loop Left JoinJoin Filter: (false AND (t2.b = 1))->  Seq Scan on pred_tab t2->  ResultOne-Time Filter: false
(8 rows)-- 全文用例12
-- 确保当列通过外连接可为空时,IS_NULL 限定符不会简化为常量 FALSE
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON t1.a = 1
postgres-#     LEFT JOIN pred_tab t3 ON t2.a IS NULL;QUERY PLAN                 
-------------------------------------------Nested Loop Left JoinJoin Filter: (t2.a IS NULL)->  Nested Loop Left JoinJoin Filter: (t1.a = 1)->  Seq Scan on pred_tab t1->  Materialize->  Seq Scan on pred_tab t2->  Materialize->  Seq Scan on pred_tab t3
(9 rows)postgres=#

案例四如下(测试连接子句中的 OR 子句):

-- 全文用例13
-- 当可证明 OR 分支始终为真时,确保忽略 OR 子句
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON TRUE
postgres-#     LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1;QUERY PLAN                    
-------------------------------------------------Nested Loop Left Join->  Seq Scan on pred_tab t1->  Materialize->  Nested Loop Left Join->  Seq Scan on pred_tab t2->  Materialize->  Seq Scan on pred_tab t3
(7 rows)postgres=#-- 全文用例14
-- 确保当外连接中的列可为空时,不会忽略 NullTest
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON t1.a = 1
postgres-#     LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1;QUERY PLAN                     
---------------------------------------------------Nested Loop Left JoinJoin Filter: ((t2.a IS NOT NULL) OR (t2.b = 1))->  Nested Loop Left JoinJoin Filter: (t1.a = 1)->  Seq Scan on pred_tab t1->  Materialize->  Seq Scan on pred_tab t2->  Materialize->  Seq Scan on pred_tab t3
(9 rows)postgres=#-- 全文用例15
-- 确保当所有 OR 分支均可证明为假时,OR 子句简化为常数 FALSE
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON TRUE
postgres-#     LEFT JOIN pred_tab t3 ON (t2.a IS NULL OR t2.c IS NULL) AND t2.b = 1;QUERY PLAN                     
---------------------------------------------------Nested Loop Left Join->  Seq Scan on pred_tab t1->  Materialize->  Nested Loop Left JoinJoin Filter: (false AND (t2.b = 1))->  Seq Scan on pred_tab t2->  ResultOne-Time Filter: false
(8 rows)postgres=#-- 全文用例16
-- 确保当外连接中的列变为可空时,OR 子句不会简化为常量 FALSE
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_tab t1
postgres-#     LEFT JOIN pred_tab t2 ON t1.a = 1
postgres-#     LEFT JOIN pred_tab t3 ON t2.a IS NULL OR t2.c IS NULL;QUERY PLAN                     
---------------------------------------------------Nested Loop Left JoinJoin Filter: ((t2.a IS NULL) OR (t2.c IS NULL))->  Nested Loop Left JoinJoin Filter: (t1.a = 1)->  Seq Scan on pred_tab t1->  Materialize->  Seq Scan on pred_tab t2->  Materialize->  Seq Scan on pred_tab t3
(9 rows)postgres=#

案例五如下(验证我们是否通过继承父级正确处理 IS NULL 和 IS NOT NULL 限定词):

postgres=# CREATE TABLE pred_parent (a int);
CREATE TABLE
postgres=# CREATE TABLE pred_child () INHERITS (pred_parent);
CREATE TABLE
postgres=# ALTER TABLE ONLY pred_parent ALTER a SET NOT NULL;
ALTER TABLE
postgres=# \d pred_parentTable "public.pred_parent"Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------a      | integer |           | not null | 
Number of child tables: 1 (Use \d+ to list them.)postgres=# \d pred_child Table "public.pred_child"Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------a      | integer |           |          | 
Inherits: pred_parentpostgres=# \d+ pred_parentTable "public.pred_parent"Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------a      | integer |           | not null |         | plain   |             |              | 
Child tables: pred_child
Access method: heappostgres=# \d+ pred_child Table "public.pred_child"Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------a      | integer |           |          |         | plain   |             |              | 
Inherits: pred_parent
Access method: heappostgres=#
-- 全文用例17
-- 确保对 pred_child 的扫描包含 IS NOT NULL 限定符
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_parent WHERE a IS NOT NULL;QUERY PLAN                  
---------------------------------------------Append->  Seq Scan on pred_parent pred_parent_1->  Seq Scan on pred_child pred_parent_2Filter: (a IS NOT NULL)
(4 rows)postgres=#-- 全文用例18
-- 确保我们只扫描 pred_child 而不扫描 pred_pa​​rent
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_parent WHERE a IS NULL;QUERY PLAN             
------------------------------------Seq Scan on pred_child pred_parentFilter: (a IS NULL)
(2 rows)postgres=#

postgres=# ALTER TABLE pred_parent ALTER a DROP NOT NULL;
ALTER TABLE
postgres=# ALTER TABLE pred_child ALTER a SET NOT NULL;
ALTER TABLE
postgres=# \d pred_parentTable "public.pred_parent"Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------a      | integer |           |          | 
Number of child tables: 1 (Use \d+ to list them.)postgres=# \d pred_child Table "public.pred_child"Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------a      | integer |           | not null | 
Inherits: pred_parentpostgres=#
-- 全文用例19
-- 确保从 pred_child 扫描中删除 IS NOT NULL 限定符。
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_parent WHERE a IS NOT NULL;QUERY PLAN                  
---------------------------------------------Append->  Seq Scan on pred_parent pred_parent_1Filter: (a IS NOT NULL)->  Seq Scan on pred_child pred_parent_2
(4 rows)postgres=#-- 全文用例20
-- 确保我们只扫描 pred_pa​​rent 而不扫描 pred_child
postgres=# EXPLAIN (COSTS OFF)
postgres-# SELECT * FROM pred_parent WHERE a IS NULL;QUERY PLAN        
-------------------------Seq Scan on pred_parentFilter: (a IS NULL)
(2 rows)postgres=#

功能实现源码解析

在SQL的操作中,几乎所有的操作(比如查询)最终都会落在实际的表上,那么在执行计划中表的表示就比较重要。PostgreSQL用RelOptInfo结构体来表示,它是执行计划路径生成的主要数据结构,同样用于表述表、子查询、函数等。

在查询优化的过程中,我们首先面对的是 FROM 子句中的表,通常称之为范围表(RangeTable),它可能是一个常规意义上的表, 也可能是一个子查询,或者还可能是一个查询结果的组织为表状的函数(例如 TableFunction),这些表处于查询执行计划树的叶子节点,是产生最终查询结果的基础,我们称之为基表,这些基表可以用 RelOptlnfo 结构体表示,它的RelOptlnfo->reloptkindRELOPT_BASEREL

基表之间可以进行连接操作,连接操作产生的“中间”结果也可以用 RelOptlnfo 结构体来表示,它对应的 RelOptlnfo->reloptkindLOPT_JOINREL 。另外还有RELOPT_OTHER_MEMBER_REL 类型的 RelOptlnfo 用来表示继承表的子表或者 UNION 操作的子查询等。由于 RelOptlnfo 结构体集多种功能于一身,因此它的体积也比较庞大,我们会随着学习的深入逐个学习和分析!

注:若是对该数据结构感兴趣,可以自行查看 张树杰 《查询优化深度探索》4.1.1章节!

RelOptInfo结构体是贯穿整个path生成过程的一个数据结构,生成路径的最终结果始终存放其中,生成和选择路径所需的许多数据也存放在其中。路径生成和选择涉及的所有操作,几乎都是针对RelOptInfo结构体操作。

  • RelOptInfo结构体涉及baserel(基本关系)和joinrel(连接关系)
  • baserel(基本关系)是一个普通表,子查询或者范围表中出现的函数
  • joinrel(连接关系)是两个或者两个以上的baserel(基本关系)在一定约束条件下的简单合并
  • 对任何一组baserel仅有一个joinrel,即对于任何给定的baserel集合,只有一个RelOptInfo结构体
/** RelOptInfo:用于规划/优化的每个关系信息*  * 出于规划目的,"base rel"要么是普通关系(表),要么是出现在范围表中的子 SELECT 或函数的输出。* 无论哪种情况,它都由 RT 索引唯一标识。* "joinrel"是两个或多个基础关系的连接。* 连接关系由其组件基础关系的 RT 索引集以及它已计算的任何外连接的 RT 索引标识。* 我们为每个基础关系和连接关系创建 RelOptInfo 节点,并将它们分别存储在 PlannerInfo 的 simple_rel_array 和 join_rel_list 中。*  * 请注意,对于任何给定的组件基础关系集,只有一个连接关系,无论我们以何种顺序组装它们;因此无序集是识别它的正确数据类型。*  * 我们还有"other rels",它们与基础关系类似,因为它们引用单个 RT 索引;但它们不是连接树的一部分,并被赋予不同的 RelOptKind 来标识它们。* 目前,唯一类型的 otherrels 是为"append relation"的成员关系(即继承集或 UNION ALL 子查询)制作的。*  * 附加关系具有父 RTE,它是基关系,代表整个附加关系。成员 RTE 是 otherrels。* 父级存在于查询连接树中,但成员不存在。* 成员 RTE 和 otherrels 用于计划对附加集的各个表或子查询的扫描;* 然后为父基关系提供 Append 和/或 MergeAppend 路径,这些路径包含各个成员关系的最佳路径。* (有关更多信息,请参阅 AppendRelInfo 的注释。)*  * 我们曾经还制作了 otherrels 来表示连接 RTE,用于处理连接别名变量。* 目前不需要这样做,因为所有连接别名变量在 preprocess_expression 期间都扩展为非别名形式。*  * 我们还有表示不同分区表的子关系之间的连接的关系。* 这些关系不会添加到 join_rel_level 列表中,因为它们不是由动态规划算法直接连接的。*  * 还有一个用于"upper"关系的 RelOptKind,它们是描述扫描/连接后处理步骤(例如聚合)的 RelOptInfos。* 这些 RelOptInfos 中的许多字段都没有意义,但它们的 Path 字段始终包含显示执行该处理步骤的方法的 Paths。*  * 此数据结构的部分内容特定于各种扫描和连接机制。似乎不值得为它们创建新的节点类型。*/

关于RelOptInfo数据结构,将是我们后面学习的重点 但是限于本篇的篇幅,我们不在展开 今天学习的重点如下:

// src/include/nodes/pathnodes.htypedef struct RelOptInfo
{.../** Zero-based set containing attnums of NOT NULL columns.  Not populated* for rels corresponding to non-partitioned inh==true RTEs.*  * 包含非空列的 attnums 的零基集。未填充与非分区 inh==true RTE 对应的 rels。*/Bitmapset  *notnullattnums;...
}

// src/backend/optimizer/util/plancat.c/** get_relation_info -*	  Retrieves catalog information for a given relation.*	  检索给定关系的目录信息** Given the Oid of the relation, return the following info into fields* of the RelOptInfo struct:**	min_attr	lowest valid AttrNumber*	max_attr	highest valid AttrNumber*	indexlist	list of IndexOptInfos for relation's indexes*	statlist	list of StatisticExtInfo for relation's statistic objects*	serverid	if it's a foreign table, the server OID*	fdwroutine	if it's a foreign table, the FDW function pointers*	pages		number of pages*	tuples		number of tuples*	rel_parallel_workers user-defined number of parallel workers** Also, add information about the relation's foreign keys to root->fkey_list.* 另外,将有关关系的外键的信息添加到 root->fkey_list。** Also, initialize the attr_needed[] and attr_widths[] arrays.  In most* cases these are left as zeroes, but sometimes we need to compute attr* widths here, and we may as well cache the results for costsize.c.* 另外,初始化 attr_needed[] 和 attr_widths[] 数组。* 在大多数情况下,这些都保留为零,但有时我们需要在这里计算 attr 宽度,我们不妨将结果缓存在 costize.c 中。** If inhparent is true, all we need to do is set up the attr arrays:* the RelOptInfo actually represents the appendrel formed by an inheritance* tree, and so the parent rel's physical size and index information isn't* important for it, however, for partitioned tables, we do populate the* indexlist as the planner uses unique indexes as unique proofs for certain* optimizations.* 如果 inhparent 为真,我们需要做的就是设置 attr 数组:* RelOptInfo 实际上代表由继承树形成的附加项,因此父级 rel 的物理大小和索引信息对它来说并不重要,* 但是,对于分区表,我们确实填充索引列表,因为规划器使用唯一索引作为某些优化的唯一证明。*/
void
get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,RelOptInfo *rel)
{.../** Record which columns are defined as NOT NULL.  We leave this* unpopulated for non-partitioned inheritance parent relations as it's* ambiguous as to what it means.  Some child tables may have a NOT NULL* constraint for a column while others may not.  We could work harder and* build a unioned set of all child relations notnullattnums, but there's* currently no need.  The RelOptInfo corresponding to the !inh* RangeTblEntry does get populated.*  * 记录哪些列被定义为 NOT NULL。* 对于非分区继承父关系,我们将其保留为未填充状态,因为它的含义不明确。* 一些子表可能对某一列具有 NOT NULL 约束,而其他子表可能没有。* 我们可以更加努力,并构建所有子关系 notnullattnums 的联合集,但目前没有必要。* 与 !inh RangeTblEntry 对应的 RelOptInfo 确实被填充。*/if (!inhparent || relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE){for (int i = 0; i < relation->rd_att->natts; i++){Form_pg_attribute attr = TupleDescAttr(relation->rd_att, i);if (attr->attnotnull){rel->notnullattnums = bms_add_member(rel->notnullattnums,attr->attnum);/** Per RemoveAttributeById(), dropped columns will have their* attnotnull unset, so we needn't check for dropped columns* in the above condition.*  * 根据 RemoveAttributeById(),删除的列将取消设置其 attnotnull,因此我们不需要在上述条件下检查删除的列。*/Assert(!attr->attisdropped);}}}...
}

在这里插入图片描述


因为篇幅的限制,我们这里在最小范围内介绍此次patch的核心功能实现。接下来我会依次调试相关用例,剖析patch的每个功能点 该过程中其他相关的内容不过多介绍,后面会由其他博客详细分析!

简单情况的处理

永真永假的处理

表结构如下:

postgres=# \d+ pred_tab Table "public.pred_tab"Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------a      | integer |           | not null |         | plain   |             |              | b      | integer |           |          |         | plain   |             |              | c      | integer |           | not null |         | plain   |             |              | 
Access method: heappostgres=#

那么对于where条件中的IS [NOT] NULL来说,自然有永真/假的情况存在。永真 直接忽略即可;而永假则可以处理成 const false,如下:

在这里插入图片描述

此时的函数堆栈,如下:

build_simple_rel(PlannerInfo * root, int relid, RelOptInfo * parent)
add_base_rels_to_query(PlannerInfo * root, Node * jtnode)
add_base_rels_to_query(PlannerInfo * root, Node * jtnode)
query_planner(PlannerInfo * root, query_pathkeys_callback qp_callback, void * qp_extra)
grouping_planner(PlannerInfo * root, double tuple_fraction, SetOperationStmt * setops)
subquery_planner(PlannerGlobal * glob, Query * parse, PlannerInfo * parent_root, _Bool hasRecursion, double tuple_fraction, SetOperationStmt * setops)
standard_planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
pg_plan_query(Query * querytree, const char * query_string, int cursorOptions, ParamListInfo boundParams)
standard_ExplainOneQuery(Query * query, int cursorOptions, IntoClause * into, ExplainState * es, const char * queryString, ParamListInfo params, QueryEnvironment * queryEnv)
ExplainOneQuery(Query * query, int cursorOptions, IntoClause * into, ExplainState * es, const char * queryString, ParamListInfo params, QueryEnvironment * queryEnv)
ExplainQuery(ParseState * pstate, ExplainStmt * stmt, ParamListInfo params, DestReceiver * dest)
standard_ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc)
ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc)
PortalRunUtility(Portal portal, PlannedStmt * pstmt, _Bool isTopLevel, _Bool setHoldSnapshot, DestReceiver * dest, QueryCompletion * qc)
FillPortalStore(Portal portal, _Bool isTopLevel)
PortalRun(Portal portal, long count, _Bool isTopLevel, _Bool run_once, DestReceiver * dest, DestReceiver * altdest, QueryCompletion * qc)
exec_simple_query(const char * query_string)
PostgresMain(const char * dbname, const char * username)
BackendMain(char * startup_data, size_t startup_data_len)
postmaster_child_launch(BackendType child_type, char * startup_data, size_t startup_data_len, ClientSocket * client_sock)
BackendStartup(ClientSocket * client_sock)
ServerLoop()
PostmasterMain(int argc, char ** argv)
main(int argc, char ** argv)

相关函数介绍如下:

// src/backend/optimizer/plan/initsplan.c/** add_base_rels_to_query**	  Scan the query's jointree and create baserel RelOptInfos for all*	  the base relations (e.g., table, subquery, and function RTEs)*	  appearing in the jointree.*	  扫描查询的连接树并为连接树中出现的所有基本关系(例如,表、子查询和函数 RTE)*	  创建 baserel RelOptInfos** The initial invocation must pass root->parse->jointree as the value of* jtnode.  Internally, the function recurses through the jointree.* 初始调用必须传递 root->parse->jointree 作为 jtnode 的值。* 在内部,该函数通过 jointree 进行递归。** At the end of this process, there should be one baserel RelOptInfo for* every non-join RTE that is used in the query.  Some of the baserels* may be appendrel parents, which will require additional "otherrel"* RelOptInfos for their member rels, but those are added later.* 在此过程结束时,查询中使用的每个非连接 RTE 都应该有一个 baserel RelOptInfo。* 一些 baserel 可能是 appendrel 父级,这将需要为其成员 rels 提供额外的“otherrel”RelOptInfo,* 但这些是稍后添加的。*/
void
add_base_rels_to_query(PlannerInfo *root, Node *jtnode);

然后在接下来的逻辑里调用get_relation_info以得到的notnullattnums非空列位图信息(这个逻辑后面就不再赘述了),如下:

get_relation_info(PlannerInfo * root, Oid relationObjectId, _Bool inhparent, RelOptInfo * rel)
build_simple_rel(PlannerInfo * root, int relid, RelOptInfo * parent)
add_base_rels_to_query(PlannerInfo * root, Node * jtnode)
add_base_rels_to_query(PlannerInfo * root, Node * jtnode)
query_planner(PlannerInfo * root, query_pathkeys_callback qp_callback, void * qp_extra)
grouping_planner(PlannerInfo * root, double tuple_fraction, SetOperationStmt * setops)
subquery_planner(PlannerGlobal * glob, Query * parse, PlannerInfo * parent_root, _Bool hasRecursion, double tuple_fraction, SetOperationStmt * setops)
standard_planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
...

在这里插入图片描述


接下来就到了构建 RestrictInfo 节点本身,约束条件构建 如下:

在这里插入图片描述

约束条件就是 WHERE/ON/HAYING 子句中的各个条件, 在查询树中 ,它们以表达式(Expr)的方式存在,主要存放在 FromExprquals 链表中和 JoinExprquals 链表中,在本书中对于这类条件通常统称为约束条件,但在分解连接树的过程中,这部分还需要详细地分为过滤(Filter )条件连接( Join )条件。通常而言,出现 WHERE 子句中的条件是过滤条件,出现在 ON 子句中的条件是连接条件,但是随着约束条件的下推,能下推的连接条件也会转化成过滤条件。

注:若是对该条件生成过程感兴趣,可以自行查看 张树杰 《查询优化深度探索》4.3.3.6章节!


继续向下,来到了distribute_restrictinfo_to_rels函数:

// src/backend/optimizer/plan/initsplan.c
/** distribute_restrictinfo_to_rels*	  Push a completed RestrictInfo into the proper restriction or join*	  clause list(s).*	  将已完成的 RestrictInfo 推送到适当的限制或连接子句列表中。** This is the last step of distribute_qual_to_rels() for ordinary qual* clauses.  Clauses that are interesting for equivalence-class processing* are diverted to the EC machinery, but may ultimately get fed back here.* 这是对普通 qual 子句执行 deliver_qual_to_rels() 的最后一步。* 对等价类处理感兴趣的子句被转移到 EC 机制,但最终可能会在这里得到反馈。*/
void
distribute_restrictinfo_to_rels(PlannerInfo *root,RestrictInfo *restrictinfo);

在这里插入图片描述

在这里插入图片描述

然后就到了此次patch最核心的实现逻辑,如下:

// src/backend/optimizer/plan/initsplan.c/** add_base_clause_to_rel*		Add 'restrictinfo' as a baserestrictinfo to the base relation denoted*		by 'relid'.  We offer some simple prechecks to try to determine if the*		qual is always true, in which case we ignore it rather than add it.*		If we detect the qual is always false, we replace it with*		constant-FALSE.*	 *	将“restrictinfo”作为 baserestrictinfo 添加到“relid”表示的基本关系中。*	我们提供一些简单的预检查,尝试确定该限定词是否始终为真,在这种情况下,我们会忽略它而不是添加它。*	如果我们检测到该限定词始终为假,我们会将其替换为 constant-FALSE。*/
static void
add_base_clause_to_rel(PlannerInfo *root, Index relid,RestrictInfo *restrictinfo)
{RelOptInfo *rel = find_base_rel(root, relid);RangeTblEntry *rte = root->simple_rte_array[relid];Assert(bms_membership(restrictinfo->required_relids) == BMS_SINGLETON);/** For inheritance parent tables, we must always record the RestrictInfo* in baserestrictinfo as is.  If we were to transform or skip adding it,* then the original wouldn't be available in apply_child_basequals. Since* there are two RangeTblEntries for inheritance parents, one with* inh==true and the other with inh==false, we're still able to apply this* optimization to the inh==false one.  The inh==true one is what* apply_child_basequals() sees, whereas the inh==false one is what's used* for the scan node in the final plan.* 对于继承父表,我们必须始终按原样将 RestrictInfo 记录在 baserestrictinfo 中。* 如果我们要转换或跳过添加它,那么原始内容将无法在 apply_child_basequals 中使用。* 由于有两个用于继承父表的 RangeTblEntries,一个带有 inh==true,另一个带有 inh==false,* 因此我们仍然可以将此优化应用于 inh==false。* inh==true 是 apply_child_basequals() 看到的,而 inh==false 是最终计划中用于扫描节点的内容。** We make an exception to this for partitioned tables.  For these, we* always apply the constant-TRUE and constant-FALSE transformations.  A* qual which is either of these for a partitioned table must also be that* for all of its child partitions.* 对于分区表,我们对此做了例外处理。* 对于这些表,我们始终应用常量 TRUE 和常量 FALSE 转换。* 对于分区表,符合上述任一条件的限定词也必须是其所有子分区的限定词。*/if (!rte->inh || rte->relkind == RELKIND_PARTITIONED_TABLE){/* Don't add the clause if it is always true */// 如果该子句始终为真,则不要添加该子句if (restriction_is_always_true(root, restrictinfo))return;/** Substitute the origin qual with constant-FALSE if it is provably* always false.  Note that we keep the same rinfo_serial.* * 如果可以证明 origin qual 总是 false,则用 constant-FALSE 替换 origin qual。* 请注意,我们保留相同的 rinfo_serial。*/if (restriction_is_always_false(root, restrictinfo)){int			save_rinfo_serial = restrictinfo->rinfo_serial;restrictinfo = make_restrictinfo(root,(Expr *) makeBoolConst(false, false),restrictinfo->is_pushed_down,restrictinfo->has_clone,restrictinfo->is_clone,restrictinfo->pseudoconstant,0, /* security_level */restrictinfo->required_relids,restrictinfo->incompatible_relids,restrictinfo->outer_relids);restrictinfo->rinfo_serial = save_rinfo_serial;}}/* Add clause to rel's restriction list */rel->baserestrictinfo = lappend(rel->baserestrictinfo, restrictinfo);/* Update security level info */rel->baserestrict_min_security = Min(rel->baserestrict_min_security,restrictinfo->security_level);
}

对于我们这个永真的用例1来说,restriction_is_always_true返回真 那么就不会向rel->baserestrictinfo里面append条件,于是这个就相当于做了一个忽略!

/** expr_is_nonnullable*	  Check to see if the Expr cannot be NULL** If the Expr is a simple Var that is defined NOT NULL and meanwhile is not* nulled by any outer joins, then we can know that it cannot be NULL.*/
static bool
expr_is_nonnullable(PlannerInfo *root, Expr *expr)
{RelOptInfo *rel;Var		   *var;/* For now only check simple Vars */if (!IsA(expr, Var))return false;var = (Var *) expr;/* could the Var be nulled by any outer joins? */if (!bms_is_empty(var->varnullingrels))return false;/* system columns cannot be NULL */if (var->varattno < 0)return true;/* is the column defined NOT NULL? */rel = find_base_rel(root, var->varno);if (var->varattno > 0 &&bms_is_member(var->varattno, rel->notnullattnums))return true;return false;
}/** restriction_is_always_true*	  Check to see if the RestrictInfo is always true.** Currently we only check for NullTest quals and OR clauses that include* NullTest quals.  We may extend it in the future.*/
bool
restriction_is_always_true(PlannerInfo *root,RestrictInfo *restrictinfo)
{/* Check for NullTest qual */if (IsA(restrictinfo->clause, NullTest)){NullTest   *nulltest = (NullTest *) restrictinfo->clause;/* is this NullTest an IS_NOT_NULL qual? */if (nulltest->nulltesttype != IS_NOT_NULL)return false;return expr_is_nonnullable(root, nulltest->arg);}/* If it's an OR, check its sub-clauses */if (restriction_is_or_clause(restrictinfo)){ListCell   *lc;Assert(is_orclause(restrictinfo->orclause));/** if any of the given OR branches is provably always true then the* entire condition is true.*/foreach(lc, ((BoolExpr *) restrictinfo->orclause)->args){Node	   *orarg = (Node *) lfirst(lc);if (!IsA(orarg, RestrictInfo))continue;if (restriction_is_always_true(root, (RestrictInfo *) orarg))return true;}}return false;
}/** restriction_is_always_false*	  Check to see if the RestrictInfo is always false.** Currently we only check for NullTest quals and OR clauses that include* NullTest quals.  We may extend it in the future.*/
bool
restriction_is_always_false(PlannerInfo *root,RestrictInfo *restrictinfo)
{/* Check for NullTest qual */if (IsA(restrictinfo->clause, NullTest)){NullTest   *nulltest = (NullTest *) restrictinfo->clause;/* is this NullTest an IS_NULL qual? */if (nulltest->nulltesttype != IS_NULL)return false;return expr_is_nonnullable(root, nulltest->arg);}/* If it's an OR, check its sub-clauses */if (restriction_is_or_clause(restrictinfo)){ListCell   *lc;Assert(is_orclause(restrictinfo->orclause));/** Currently, when processing OR expressions, we only return true when* all of the OR branches are always false.  This could perhaps be* expanded to remove OR branches that are provably false.  This may* be a useful thing to do as it could result in the OR being left* with a single arg.  That's useful as it would allow the OR* condition to be replaced with its single argument which may allow* use of an index for faster filtering on the remaining condition.*/foreach(lc, ((BoolExpr *) restrictinfo->orclause)->args){Node	   *orarg = (Node *) lfirst(lc);if (!IsA(orarg, RestrictInfo) ||!restriction_is_always_false(root, (RestrictInfo *) orarg))return false;}return true;}return false;
}

在这里插入图片描述

对于我们这个永假的用例3来说,restriction_is_always_false返回真 那么就会向rel->baserestrictinfo里面append一个常量假的条件,于是这个就相当于做了一个转换!

关于上面永真永假的实现函数restriction_is_always_truerestriction_is_always_false不再赘述!


or语句的处理

对于这个含有永真的用例5来说,restriction_is_always_true返回真 那么整个过滤条件都将被忽略,如下:

在这里插入图片描述

而对于or两边都为永假的用例7来说,restriction_is_always_false返回真 那么整个过滤条件都将被裁剪为const false,如下:

在这里插入图片描述

而对于or两边并非所有分支都可证明为真假的用例8来说,restriction_is_always_truerestriction_is_always_false都无法返回真 那么整个过滤条件得以保留,如下:

在这里插入图片描述

注:在下以为 这里其实还是可以继续裁剪下去,后面我将试着写一版patch去实现一下,本文不再赘述!


这里小结一下:此次patch对于以下几种的or处理:

  • 1 or 1 —> 1
  • 1 or 0 —> 1
  • 1 or unknown —> 1
  • 0 or 0 —> 0
  • 0 or unknown —> 不变

连接情况的处理

这块的新增逻辑,如下:

// src/backend/optimizer/util/joininfo.c/** add_join_clause_to_rels*	  Add 'restrictinfo' to the joininfo list of each relation it requires.*	  将“restrictinfo”添加到其所需的每个关系的 joininfo 列表中** Note that the same copy of the restrictinfo node is linked to by all the* lists it is in.  This allows us to exploit caching of information about* the restriction clause (but we must be careful that the information does* not depend on context).* 请注意,restrictinfo 节点的相同副本与其所在的所有列表链接。* 这使我们能够利用有关限制子句的信息缓存(但我们必须注意,信息不依赖于上下文)。** 'restrictinfo' describes the join clause* 'join_relids' is the set of relations participating in the join clause*				 (some of these could be outer joins)*	'restrictinfo' 描述连接子句 'join_relids' 是参与连接子句的关系集(其中一些可能是外连接)*/
void
add_join_clause_to_rels(PlannerInfo *root,RestrictInfo *restrictinfo,Relids join_relids)
{int			cur_relid;/* Don't add the clause if it is always true */if (restriction_is_always_true(root, restrictinfo))return;/** Substitute constant-FALSE for the origin qual if it is always false.* Note that we keep the same rinfo_serial.* 如果 origin qual 始终为 false,则用 constant-FALSE 代替。* 请注意,我们保留相同的 rinfo_serial。*/if (restriction_is_always_false(root, restrictinfo)){int			save_rinfo_serial = restrictinfo->rinfo_serial;restrictinfo = make_restrictinfo(root,(Expr *) makeBoolConst(false, false),restrictinfo->is_pushed_down,restrictinfo->has_clone,restrictinfo->is_clone,restrictinfo->pseudoconstant,0, /* security_level */restrictinfo->required_relids,restrictinfo->incompatible_relids,restrictinfo->outer_relids);restrictinfo->rinfo_serial = save_rinfo_serial;}...
}

有了上面的铺垫,接下来的join 就比较好理解了。不过我们这里给上述表插入一行数据,将更好的理解上面join的执行计划:

postgres=# table pred_tab ;a | b | c 
---+---+---2 |   | 2
(1 row)postgres=# \d+ pred_tab Table "public.pred_tab"Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------a      | integer |           | not null |         | plain   |             |              | b      | integer |           |          |         | plain   |             |              | c      | integer |           | not null |         | plain   |             |              | 
Access method: heappostgres=#

是否可忽略

首先看一下用例9,这里能够直接忽略的原因 如下:

postgres=# SELECT * FROM pred_tab t1 LEFT JOIN pred_tab t2 ON TRUE;a | b | c | a | b | c 
---+---+---+---+---+---2 |   | 2 | 2 |   | 2
(1 row)postgres=#

嵌套的left join的t2.a IS NOT NULL永真 && 非空!

而用例10就不一定了,如下:

postgres=# SELECT * FROM pred_tab t1 LEFT JOIN pred_tab t2 ON t1.a = 1;a | b | c | a | b | c 
---+---+---+---+---+---2 |   | 2 |   |   |  
(1 row)postgres=#

是否可简化

用例11被简化为常量 FALSE,可以参考用例9;用例12不能被简化为常量 FALSE,可以参考用例10。


or语句的处理

用例13可以直接忽略,可以参考用例9;用例14不可以直接忽略,可以参考用例10。

用例15被简化为常量 FALSE,可以参考用例9;用例16不能被简化为常量 FALSE,可以参考用例10。


父子继承的处理

patch2,如下:

SHA-1: 3af7040985b6df504a72cd307aad5d69ac5f5384* Fix IS [NOT] NULL qual optimization for inheritance tables

b262ad440 添加了代码,让规划器删除多余的 IS NOT NULL 限定符,并消除在限定符的列具有 NOT NULL 约束的表上对 IS NULL 限定符进行不必要的扫描。


该提交未考虑到继承父表在父表和子表之间可能具有不同的 NOT NULL 约束。这会导致问题,就好像我们在父表上消除了限定符,当在 apply_child_basequals() 中将限定符应用于子表时,该限定符可能未添加到父表的 baserestrictinfo 中。


在这里,我们通过不应用优化来删除属于继承父表的 RelOptInfos 的冗余限定符,并在 apply_child_basequals() 中再次应用优化来解决这个问题。实际上,这意味着父表和子表被独立考虑,因为父表具有 inh=true 和 inh=false RTE,并且我们仍然将优化应用于与 inh=false RTE 相对应的 RelOptInfo。


我们仍然可以对分区表应用 add_base_clause_to_rel() 中的优化,因为分区的 NULL 性必须与其父分区的 NULL 性相匹配。而且,如果我们扩展了 requirement_is_always_false() 和 requirement_is_always_true() 来处理分区约束,那么我们可以应用相同的逻辑,即使在多级分区表中,当 qual 与分区表父分区的分区 qual 不匹配时,也无法将值路由到分区。CHECK 约束也是如此,因为它们也必须在未分区表和其分区之间匹配。

关于继承表的使用上,可以参考这位老哥的博客:

  • postgresql表继承详解,点击前往

postgres=# select relname, relkind, oid from pg_class where relname like 'pred_%';relname   | relkind |  oid  
-------------+---------+-------pred_child  | r       | 16412pred_parent | r       | 16409
(2 rows)postgres=#

接下来先看一下用例17,父表的NOT NULL没有继承给子表,那么对pred_parent这样的查询 就需要分开处理。

正如get_relation_info函数中注释的说明:

对于非分区继承父关系,我们将其保留为未填充状态,因为它的含义不明确。一些子表可能对某一列具有 NOT NULL 约束,而其他子表可能没有。我们可以更加努力,并构建所有子关系 notnullattnums 的联合集,但目前没有必要。

为查询中使用的所有基本关系构建 RelOptInfo 节点,第一次处理 如下:

在这里插入图片描述

top restrictinfo 其clause内容,如下:

在这里插入图片描述

当然在add_base_clause_to_rel也不会过滤掉/处理这个条件,如下:

在这里插入图片描述

继续,如下:

// src/backend/optimizer/plan/planmain.cRelOptInfo *
query_planner(PlannerInfo *root,query_pathkeys_callback qp_callback, void *qp_extra)
{
.../** Now expand appendrels by adding "otherrels" for their children.  We* delay this to the end so that we have as much information as possible* available for each baserel, including all restriction clauses.  That* let us prune away partitions that don't satisfy a restriction clause.* Also note that some information such as lateral_relids is propagated* from baserels to otherrels here, so we must have computed it already.*  * 现在通过为其子级添加“otherrels”来扩展附加节点。* 我们将此操作延迟到最后,以便我们为每个基本节点提供尽可能多的信息,包括所有限制子句。* 这让我们可以删除不满足限制子句的分区。* 还请注意,此处某些信息(例如 lateral_relids)从基本节点传播到其他节点,因此我们必须已经计算了它。*/add_other_rels_to_query(root);
...
}

因为这是继承表 (这里在查询父表数据的时候,对应的继承表数据也被查询出来),如下:

在这里插入图片描述

/** find_all_inheritors -* * 返回关系 OID 列表,包括给定的 rel 以及所有直接或间接继承自该 rel 的关系。它还可以返回在以给定 rel 为根的继承树中为每个此类关系找到的父级数量(可选)* * 在所有子关系上获取指定的锁定类型(但不在给定的 rel 上;调用者应该已经锁定了它)。* 如果 lockmode 为 NoLock,则不会获取任何锁定,但调用者必须注意可能出现的子关系 DROP 竞争条件。* * 注意 - 此例程的当前调用者对子级同时分离不感兴趣,因此没有规定将它们包括在内。*/

然后在这两次循环中,也创建其他相关的 RelOptInfo。如下:

在这里插入图片描述

此时的函数堆栈,如下:

apply_child_basequals(PlannerInfo * root, RelOptInfo * parentrel, RelOptInfo * childrel, RangeTblEntry * childRTE, AppendRelInfo * appinfo)
build_simple_rel(PlannerInfo * root, int relid, RelOptInfo * parent)
expand_inherited_rtentry(PlannerInfo * root, RelOptInfo * rel, RangeTblEntry * rte, Index rti)
add_other_rels_to_query(PlannerInfo * root)
query_planner(PlannerInfo * root, query_pathkeys_callback qp_callback, void * qp_extra)
...

对于第一个子表(也就是父表本身),因为非空的存在 所以处理childrel->baserestrictinfo = NULL,如下(相当于忽略):

在这里插入图片描述

对于第二个子表(也就是子表本身),因为没有非空的存在 所以处理childrel->baserestrictinfo = childquals,如下(没有忽略):

在这里插入图片描述


有了上面的铺垫,用例18 非常好理解:第一个子表 非空的存在 条件永假,所以处理 如下:

		if (!apply_child_basequals(root, parent, rel, rte, appinfo)) // false{/** Restriction clause reduced to constant FALSE or NULL.  Mark as* dummy so we won't scan this relation.* 限制子句简化为常量 FALSE 或 NULL。标记为虚拟,这样我们就不会扫描此关系。*/mark_dummy_rel(rel);}

而经过如下alter之后,父表和子表的非空限制 颠倒过来,自然 用例19 20将有不同的呈现!

postgres=# ALTER TABLE pred_parent ALTER a DROP NOT NULL;
ALTER TABLE
postgres=# ALTER TABLE pred_child ALTER a SET NOT NULL;
ALTER TABLE
postgres=#

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395205.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

低代码之殇

低代码的浪潮已经持续几年了&#xff0c;很多声音冒出来&#xff0c;其中最刺耳就是&#xff1a;低代码就是个伪命题&#xff0c;根本不可能用低代码开发业务系统&#xff0c;尤其是复杂的业务系统&#xff1b; 更有甚者&#xff0c;直接给给低代码贴了标签&#xff1a;骗子 …

linux常见性能监控工具

常用命令top、free 、vmsata、iostat 、sar命令 具体更详细命令可以查看手册&#xff0c;这里只是简述方便找工具 整体性能top,内存看free&#xff0c;磁盘cpu内存历史数据可以vmsata、iostat 、sar、iotop top命令 交互&#xff1a;按P按照CPU排序&#xff0c;按M按照内存…

trie算法

1、定义 高效的存储和查找字符串集合的数据结构 它的优点是&#xff1a;利用字符串的公共前缀来减少查询时间&#xff0c;最大限度地减少无谓的字符串比较&#xff0c;查询效率比哈希树高 2、构建 我们可以使用数组来模拟实现Trie树。 我们设计一个二维数组 son[N] [26] 来…

鸿蒙HarmonyOS开发:@Observed装饰器和@ObjectLink装饰器:监听嵌套类对象属性变化

文章目录 一、装饰器二、概述三、限制条件四、装饰器说明五、Toggle组件1、子组件2、接口3、ToggleType枚举4、事件 六、示例演示1、代码2、效果 一、装饰器 State装饰器&#xff1a;组件内状态Prop装饰器&#xff1a;父子单向同步Link装饰器&#xff1a;父子双向同步Provide装…

SpringBoot MybatisPlus selectOne的坑

目录 一、问题 二、问题解决 三、其他方法 一、问题 selectOne在查询多条数据时会报错&#xff0c;查询语句并不会加 limit 1。 One record is expected, but the query result is multiple records。 二、问题解决 在QueryWrapper上添加如下&#xff1a; QueryWrapper&…

支付宝开放平台竟出现一张神秘人脸!

前言 ​ 我因一个单子来到支付宝开放平台来。在将其加入书签的时候&#xff0c;我发现出现了个神秘的人脸 一张笑容明媚的脸&#xff0c;就是出现的时候不太对 正常的收藏网址 应该是显示对应log 就不继续找相关例子了 ​ 添加书签的页面&#xff0c;本该出现log的地方缺出现了…

VScode的环境编译器选择

按快捷键 Ctrl Shift P 选择即可

反向传播与梯度累积

反向传播算法&#xff1a;loss.backward()的实现细节 向前传播&#xff1a;输入数据得到预测结果。向后传播&#xff1a;计算梯度加更新参数。反向传播&#xff1a;计算梯度 计算图 计算图 有向无环图 基本运算 节点&#xff1a;变量节点 & 计算节点有向边&#xff1…

【LeetCode】48. 旋转图像

旋转图像 题目描述&#xff1a; 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在原地旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1&#xff1a; 输入&#xff1a;matrix …

【维修经验分享】可调电源输出不稳定

一、前言 从今天这期开始&#xff0c;我将在“维修经验”这个专栏中分享一些简单的电路板维修经历&#xff0c;希望能帮助大家。 二、相关信息及问题 型号&#xff1a;迈胜MS-3050。 问题&#xff1a;输出电压无法稳定下来&#xff0c;一直在跳动。这款电源估计也比较老了&…

Linux系统之ls命令的基本使用

Linux系统之ls命令的基本使用 一、ls命令介绍二、ls命令的使用帮助2.1 命令格式2.2 命令选项2.3 使用帮助 三、ls命令的基本使用3.1 列出当前目录中的所有文件和目录3.2 列出指定目录中的所有文件和目录3.3 显示文件的详细信息3.4 列出所有文件和目录3.5 显示目录本身&#xff…

【单片机毕业设计选题24105】-基于单片机的自动配料机控制系统

系统功能: 系统分为自动状态和手动状态上电默认为手动状态&#xff0c;手动状态下可以通过按键和蓝牙手动控制步进电机正反转&#xff0c; 自动状态下根据采集到的两路接近传感器信号自动控制步进电机正反转。 系统上电后&#xff0c;OLED显示“欢迎使用请稍后”&#xff0c…

C++17常用新特性介绍

目录 1、结构化绑定 2、constexpr扩展 2.1、constexpr lambda 2.2、constexpr if 2.3、constexpr string 4、if with initializer 5、std::optional 6、使用inline定义内联变量 7、std::filesystem库 8、折叠表达式 9、模板的模板参数推导 9.1、从构造函数参数推导…

前端获取视频文件宽高信息和视频时长

安装 yarn add video-metadata-thumbnails | npm install video-metadata-thumbnails引入依赖包 import { getMetadata } from video-metadata-thumbnails使用 if (file.name.includes(mp4)) {if (file) {try {console.log(file)// 获取视频的元数据const metadata await …

【微信小程序开发】——奶茶点餐小程序的制作(一)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

51单片机—智能垃圾桶(定时器)

一. 定时器 1. 简介 C51中的定时器和计数器是同一个硬件电路支持的&#xff0c;通过寄存器配置不同&#xff0c;就可以将他当做定时器或者计数器使用。 确切的说&#xff0c;定时器和计数器区别是致使他们背后的计数存储器加1的信号不同。当配置为定时器使用时&#xff0c;每…

数据结构的基本概念

数据结构的基本概念 数据是什么&#xff1f; 数据 &#xff1a; 数据是信息的载体&#xff0c;是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别&#xff08;二进制0|1&#xff09;和处理的符号的集合。数据是计算机程序加工的原料。 早期计算机处理的…

【代码随想录】有序数组的平方

本博文为《代码随想录》的学习笔记&#xff0c;原文链接&#xff1a;代码随想录 题目 977. 有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&…

【物联网设备端开发】使用QEMU模拟ESP硬件运行ESP-IDF

目录 一&#xff0c;开发环境搭建 1.1 安装ESP-IDF 1.2 安装vscode插件 1.3 在ESP-IDF插件配置ESP-IDF开发配置 1.4 下载IOTDeviceSDK 设备端开发代码 1.5 通过ESP-IDF插件编译好镜像 1.6 构建QEMU docker镜像 1.7 使用QEMU容器运行镜像 二&#xff0c;搭建QEMU环境步…

PTrade常见问题系列22

反馈定义的上午7点执行run_daily函数&#xff0c;但是每周一上午都没法正常执行&#xff1f; 1、run_daily函数加载在initialize函数中&#xff0c;执行后才会创建定时任务&#xff1b; 2、由于周末会有例行重启操作&#xff0c;在重启以后拉起交易时相当于非交易日启动的交易…