PostgreSQL 源码解读(42)- 查询语句#27(等价类)

上一小节介绍了函数query_planner中子函数子函数build_base_rel_tlists/find_placeholders_in_jointree/find_lateral_references的实现逻辑,本节介绍下一个子函数deconstruct_jointree函数中涉及的数学知识:等价类以及等价类在PG中的应用实例。

一、数学基础

等价关系
等价关系(equivalence relation)即设R是某个集合A上的一个二元关系。若R满足以下条件:
1.自反性:∀x∈A,xRx
2.对称性:∀x,y∈A,xRy ⇒ yRx
3.传递性:∀x,y,z∈A,(xRy ∧ yRz) ⇒ xRz
则称R是一个定义在A上的等价关系。习惯上会把等价关系的符号由R改写为∼
详细的说明和例子详见维基百科

等价类
假设在一个集合A上定义一个等价关系(用∼表示),则A中的某个元素x的等价类就是在A中等价于x的所有元素所形成的子集:
[x] = {y∈A,y∼x}
详细的说明和例子详见维基百科

二、应用

PG在函数deconstruct_jointree中对约束条件子句(qual clauses)进行扫描,可能会在非外连接的连接条件中找到等式,如A=B,这时候会创建一个等价类(记作{A,B}).如果在后面发现另一个等式,如B=C,则把C加到已存在的等价类{A,B}中,即{A,B,C}。通过这个步骤,就产生了一些等价类,这些等价类中任何一对成员的等值关系都可以作为约束或者连接的条件.
这样做的好处,一是可以通过这样的简化,只需要验证部分等值条件,而不需要验证所有的等值条件,就可以去掉一些多余的等价类条件约束;二是从相反的方向上考量,优化器在准备一个约束或者连接条件列表的时候,检查每个等价类是否能够派生新的满足当前约束或者连接的隐式条件。例如在上面的例子中,可以依据等价类{A,B,C}产生一个A=C的条件,这样优化器就可以尝试探索新的优化连接路径。

例一:

testdb=# explain verbose select t1.dwbh,t2.grbh
testdb-# from t_dwxx t1,t_grxx t2
testdb-# where t1.dwbh = t2.dwbh and t1.dwbh = '1001';
                               QUERY PLAN                               
------------------------------------------------------------------------
 Nested Loop  (cost=0.00..16.06 rows=2 width=76)
   Output: t1.dwbh, t2.grbh -- 连接,无需执行filter操作
   ->  Seq Scan on public.t_dwxx t1  (cost=0.00..1.04 rows=1 width=38)
         Output: t1.dwmc, t1.dwbh, t1.dwdz
         Filter: ((t1.dwbh)::text = '1001'::text) -- 连接前完成选择操作
   ->  Seq Scan on public.t_grxx t2  (cost=0.00..15.00 rows=2 width=76)
         Output: t2.dwbh, t2.grbh, t2.xm, t2.xb, t2.nl
         Filter: ((t2.dwbh)::text = '1001'::text) -- 连接前完成选择操作
(8 rows)

约束条件:t1.dwbh = t2.dwbh and t1.dwbh = '1001',其相应的等价类可以简略的理解为:{t1.dwbh,t2.dwbh,'1001'},根据传递性,那么可以推导出隐性约束条件:t2.dwbh='1001',在连接前把谓词t1.dwbh='1001'和t2.dwbh='1002'下推到基表扫描中,减少连接的元组数量,从而实现查询优化.从查询计划来看,PG实际也是这样处理的.
下面的SQL语句则不具备以上条件,因此在连接过程中还需要执行join filter.

testdb=# explain verbose select t1.dwbh,t2.grbh
testdb-# from t_dwxx t1,t_grxx t2
testdb-# where t1.dwbh = t2.dwbh and t1.dwdz like '广东省%';
                                QUERY PLAN                                
--------------------------------------------------------------------------
 Nested Loop  (cost=0.00..20.04 rows=2 width=76)
   Output: t1.dwbh, t2.grbh
   Join Filter: ((t1.dwbh)::text = (t2.dwbh)::text)
   ->  Seq Scan on public.t_dwxx t1  (cost=0.00..1.04 rows=1 width=38)
         Output: t1.dwmc, t1.dwbh, t1.dwdz
         Filter: ((t1.dwdz)::text ~~ '广东省%'::text)
   ->  Seq Scan on public.t_grxx t2  (cost=0.00..14.00 rows=400 width=76)
         Output: t2.dwbh, t2.grbh, t2.xm, t2.xb, t2.nl
(8 rows)

例二:
测试脚本如下:

testdb=# explain verbose select t1.dwbh,t2.grbh
from t_dwxx t1,t_grxx t2
where t1.dwbh = t2.dwbh 
order by t2.dwbh;
                                    QUERY PLAN                                     
-----------------------------------------------------------------------------------
 Sort  (cost=20.18..20.19 rows=6 width=114)
   Output: t1.dwbh, t2.grbh, t2.dwbh
   Sort Key: t1.dwbh
   ->  Hash Join  (cost=1.07..20.10 rows=6 width=114)
         Output: t1.dwbh, t2.grbh, t2.dwbh
         Inner Unique: true
         Hash Cond: ((t2.dwbh)::text = (t1.dwbh)::text)
         ->  Seq Scan on public.t_grxx t2  (cost=0.00..14.00 rows=400 width=76)
               Output: t2.dwbh, t2.grbh, t2.xm, t2.xb, t2.nl
         ->  Hash  (cost=1.03..1.03 rows=3 width=38)
               Output: t1.dwbh
               ->  Seq Scan on public.t_dwxx t1  (cost=0.00..1.03 rows=3 width=38)
                     Output: t1.dwbh
(13 rows)

从执行计划来看,虽然明确指定按t2.dwbh进行排序,但实际上按t1.dwbh进行排序.原因是存在等价类{t1.dwbh,t2.dwbh},不管在t1.dwbh还是t2.dwbh上排序,结果都是一样的,但t1.dwbh上排序,成本更低,因此优先选择此Key.

值得注意的是:PG的等价类只有在有等式条件(不包括外连接)和排序的情况下才产生,作为优化的一个方向可以考虑存在不等式时,把谓词进行下推.

testdb=# explain verbose select t1.dwbh,t2.grbh
from t_dwxx t1,t_grxx t2
where t1.dwbh = t2.dwbh and t1.dwbh > '1001';
                                QUERY PLAN                                
--------------------------------------------------------------------------
 Nested Loop  (cost=0.00..20.04 rows=2 width=76)
   Output: t1.dwbh, t2.grbh
   Join Filter: ((t1.dwbh)::text = (t2.dwbh)::text) -- 这一步必不可少
   ->  Seq Scan on public.t_dwxx t1  (cost=0.00..1.04 rows=1 width=38)
         Output: t1.dwmc, t1.dwbh, t1.dwdz
         Filter: ((t1.dwbh)::text > '1001'::text) -- Filter过滤
   ->  Seq Scan on public.t_grxx t2  (cost=0.00..14.00 rows=400 width=76)
         Output: t2.dwbh, t2.grbh, t2.xm, t2.xb, t2.nl -- 全表扫描,可以考虑加入Filter
(8 rows)

三、参考资料

等价关系
等价类
关系代数
查询优化基础

附:query_planner中的代码片段

     //...
     /*
      * Examine the targetlist and join tree, adding entries to baserel
      * targetlists for all referenced Vars, and generating PlaceHolderInfo
      * entries for all referenced PlaceHolderVars.  Restrict and join clauses
      * are added to appropriate lists belonging to the mentioned relations. We
      * also build EquivalenceClasses for provably equivalent expressions. The
      * SpecialJoinInfo list is also built to hold information about join order
      * restrictions.  Finally, we form a target joinlist for make_one_rel() to
      * work from.
      */
     build_base_rel_tlists(root, tlist);//构建"base rels"的投影列
 
     find_placeholders_in_jointree(root);//处理jointree中的PHI
 
     find_lateral_references(root);//处理jointree中Lateral依赖
 
     joinlist = deconstruct_jointree(root);//分解jointree

     /*
      * Reconsider any postponed outer-join quals now that we have built up
      * equivalence classes.  (This could result in further additions or
      * mergings of classes.)
      */
     reconsider_outer_join_clauses(root);//已创建等价类,那么需要重新考虑被下推后处理的外连接表达式
 
     /*
      * If we formed any equivalence classes, generate additional restriction
      * clauses as appropriate.  (Implied join clauses are formed on-the-fly
      * later.)
      */
     generate_base_implied_equalities(root);//等价类构建后,生成因此外加的约束语句
 
     //...
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容

  • MYSQL 基础知识 1 MySQL数据库概要 2 简单MySQL环境 3 数据的存储和获取 4 MySQL基本操...
    Kingtester阅读 7,815评论 5 116
  • 观其大纲 page 01 基础知识 1 MySQL数据库概要 2 简单MySQL环境 3 数据的存储和获取 4 M...
    周少言阅读 3,158评论 0 33
  • 我一个暑假干了好多的事情比如:关山牧场,玉溪大峡谷,游泳,钢琴,葫芦丝,双盘键,,,,但是,最特别的就是一场...
    15号小石头聂瑞辰阅读 187评论 0 1
  • 风起了,听见水碰撞石头的声音 我就把爱比做给秋天的信 一封埋进土里,一封给了落叶 逃不掉生活的人呐就写成民谣和诗 ...
    周三的简书阅读 179评论 0 0