• Greenplum查询计划分析


    这里对查询计划的学习主要是对TPC-H中Query2的分析。

    1.Query的查询语句

    select
        s_acctbal,
        s_name,
        n_name,
        p_partkey,
        p_mfgr,
        s_address,
        s_phone,
        s_comment
    from
        part,
        supplier,
        partsupp,
        nation,
        region
    where
        p_partkey = ps_partkey
        and s_suppkey = ps_suppkey
        and p_size = 6
        and p_type like '%COPPER'
        and s_nationkey = n_nationkey
        and n_regionkey = r_regionkey
        and r_name = 'AFRICA'
        and ps_supplycost = (
            select
                min(ps_supplycost)
            from
                partsupp,
                supplier,
                nation,
                region
            where
                p_partkey = ps_partkey
                and s_suppkey = ps_suppkey
                and s_nationkey = n_nationkey
                and n_regionkey = r_regionkey
                and r_name = 'AFRICA'
        )
    order by
        s_acctbal desc,
        n_name,
        s_name,
        p_partkey
    LIMIT 100;

    2.查看查询计划

    Greenplum中有语句可以查看查询计划,使用explain命令即可:

    例:testDB=#explain select * from test1;

    所以Query2的查询计划查看命令即Query2的语句之前加explain。

    3.查询中涉及到的表

    testdb=# d part
                                 Append-Only Columnar Table "public.part"
        Column     |         Type          |                        Modifiers                         
    ---------------+-----------------------+----------------------------------------------------------
     p_partkey     | bigint                | not null default nextval('part_p_partkey_seq'::regclass)
     p_name        | character varying(55) | 
     p_mfgr        | character(25)         | 
     p_brand       | character(10)         | 
     p_type        | character varying(25) | 
     p_size        | integer               | 
     p_container   | character(10)         | 
     p_retailprice | numeric               | 
     p_comment     | character varying(23) | 
    Checksum: t
    Distributed by: (p_partkey)
    
    testdb=# d partsupp
        Append-Only Columnar Table "public.partsupp"
        Column     |          Type          | Modifiers 
    ---------------+------------------------+-----------
     ps_partkey    | bigint                 | not null
     ps_suppkey    | bigint                 | not null
     ps_availqty   | integer                | 
     ps_supplycost | numeric                | 
     ps_comment    | character varying(199) | 
    Checksum: t
    Indexes:
        "idx_partsupp_partkey" btree (ps_partkey)
        "idx_partsupp_suppkey" btree (ps_suppkey)
    Distributed by: (ps_partkey, ps_suppkey)
    
    testdb=# d supplier
                                Append-Only Columnar Table "public.supplier"
       Column    |          Type          |                          Modifiers                           
    -------------+------------------------+--------------------------------------------------------------
     s_suppkey   | bigint                 | not null default nextval('supplier_s_suppkey_seq'::regclass)
     s_name      | character(25)          | 
     s_address   | character varying(40)  | 
     s_nationkey | bigint                 | not null
     s_phone     | character(15)          | 
     s_acctbal   | numeric                | 
     s_comment   | character varying(101) | 
    Checksum: t
    Indexes:
        "idx_supplier_nation_key" btree (s_nationkey)
    Distributed by: (s_suppkey)
    
    testdb=# d nation
                                 Append-Only Columnar Table "public.nation"
       Column    |          Type          |                          Modifiers                           
    -------------+------------------------+--------------------------------------------------------------
     n_nationkey | bigint                 | not null default nextval('nation_n_nationkey_seq'::regclass)
     n_name      | character(25)          | 
     n_regionkey | bigint                 | not null
     n_comment   | character varying(152) | 
    Checksum: t
    Indexes:
        "idx_nation_regionkey" btree (n_regionkey)
    Distributed by: (n_nationkey)
    
    testdb=# d region
                                 Append-Only Columnar Table "public.region"
       Column    |          Type          |                          Modifiers                           
    -------------+------------------------+--------------------------------------------------------------
     r_regionkey | bigint                 | not null default nextval('region_r_regionkey_seq'::regclass)
     r_name      | character(25)          | 
     r_comment   | character varying(152) | 
    Checksum: t
    Distributed by: (r_regionkey)

    上面是查询中涉及到的5个表。

    可以看到Greenplum使用的是列存储。

    Append-Only意思是不断追加的表,不能进行更新和删除,压缩表必须是Append-Only表。我理解Greenplum主要是处理OLAP,为了能够有更大的吞吐量,使用列存储的表结构,而列存储就可以压缩,而压缩表又必须是Append-Only表,所以表的标题都使用Appen-Only Columnar Table XXX。

    每个表最后都有Distributed by:(XXX),是表的分布键,即表是按照这个键值分布在不同的segment上的。

    4.数据库连接图

    5.查询分析结果

                                                                                      QUERY PLAN                                                                                   
    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Limit  (cost=46881.43..46881.53 rows=5 width=198)
       ->  Gather Motion 4:1  (slice10; segments: 4)  (cost=46881.43..46881.53 rows=5 width=198)
             Merge Key: public.supplier.s_acctbal, public.nation.n_name, public.supplier.s_name, part.p_partkey
             ->  Limit  (cost=46881.43..46881.44 rows=2 width=198)
                   ->  Sort  (cost=46881.43..46881.44 rows=2 width=198)
                         Sort Key (Limit): public.supplier.s_acctbal, public.nation.n_name, public.supplier.s_name, part.p_partkey
                         ->  Hash Join  (cost=42481.34..46881.39 rows=2 width=198)
                               Hash Cond: "Expr_SUBQUERY".csq_c0 = part.p_partkey AND "Expr_SUBQUERY".csq_c1 = public.partsupp.ps_supplycost
                               ->  HashAggregate  (cost=23828.08..25828.08 rows=40000 width=40)
                                     Group By: public.partsupp.ps_partkey
                                     ->  Redistribute Motion 4:4  (slice4; segments: 4)  (cost=18228.08..21428.08 rows=40000 width=40)
                                           Hash Key: public.partsupp.ps_partkey
                                           ->  HashAggregate  (cost=18228.08..18228.08 rows=40000 width=40)
                                                 Group By: public.partsupp.ps_partkey
                                                 ->  Hash Join  (cost=423.08..17428.08 rows=40001 width=16)
                                                       Hash Cond: public.partsupp.ps_suppkey = public.supplier.s_suppkey
                                                       ->  Append-only Columnar Scan on partsupp  (cost=0.00..11805.00 rows=200000 width=24)
                                                       ->  Hash  (cost=323.08..323.08 rows=2001 width=8)
                                                             ->  Broadcast Motion 4:4  (slice3; segments: 4)  (cost=9.08..323.08 rows=2001 width=8)
                                                                   ->  Hash Join  (cost=9.08..223.08 rows=501 width=8)
                                                                         Hash Cond: public.supplier.s_nationkey = public.nation.n_nationkey
                                                                         ->  Append-only Columnar Scan on supplier  (cost=0.00..149.00 rows=2500 width=16)
                                                                         ->  Hash  (cost=8.83..8.83 rows=6 width=8)
                                                                               ->  Broadcast Motion 4:4  (slice2; segments: 4)  (cost=4.16..8.83 rows=6 width=8)
                                                                                     ->  Hash Join  (cost=4.16..8.58 rows=2 width=8)
                                                                                           Hash Cond: public.nation.n_regionkey = public.region.r_regionkey
                                                                                           ->  Append-only Columnar Scan on nation  (cost=0.00..4.25 rows=7 width=16)
                                                                                           ->  Hash  (cost=4.11..4.11 rows=2 width=8)
                                                                                                 ->  Broadcast Motion 4:4  (slice1; segments: 4)  (cost=0.00..4.11 rows=2 width=8)
                                                                                                       ->  Append-only Columnar Scan on region  (cost=0.00..4.06 rows=1 width=8)
                                                                                                             Filter: r_name = 'AFRICA'::bpchar
                               ->  Hash  (cost=18623.96..18623.96 rows=489 width=214)
                                     ->  Redistribute Motion 4:4  (slice9; segments: 4)  (cost=4340.29..18623.96 rows=489 width=214)
                                           Hash Key: part.p_partkey
                                           ->  Hash Join  (cost=4340.29..18584.88 rows=489 width=214)
                                                 Hash Cond: public.partsupp.ps_suppkey = public.supplier.s_suppkey
                                                 ->  Redistribute Motion 4:4  (slice6; segments: 4)  (cost=4092.22..18287.96 rows=2443 width=58)
                                                       Hash Key: public.partsupp.ps_suppkey
                                                       ->  Hash Join  (cost=4092.22..18092.59 rows=2443 width=58)
                                                             Hash Cond: public.partsupp.ps_partkey = part.p_partkey
                                                             ->  Append-only Columnar Scan on partsupp  (cost=0.00..11805.00 rows=200000 width=24)
                                                             ->  Hash  (cost=3970.11..3970.11 rows=2443 width=34)
                                                                   ->  Broadcast Motion 4:4  (slice5; segments: 4)  (cost=0.00..3970.11 rows=2443 width=34)
                                                                         ->  Append-only Columnar Scan on part  (cost=0.00..3848.00 rows=611 width=34)
                                                                               Filter: p_size = 6 AND p_type::text ~~ '%COPPER'::text
                                                 ->  Hash  (cost=223.08..223.08 rows=501 width=172)
                                                       ->  Hash Join  (cost=9.08..223.08 rows=501 width=172)
                                                             Hash Cond: public.supplier.s_nationkey = public.nation.n_nationkey
                                                             ->  Append-only Columnar Scan on supplier  (cost=0.00..149.00 rows=2500 width=154)
                                                             ->  Hash  (cost=8.83..8.83 rows=6 width=34)
                                                                   ->  Broadcast Motion 4:4  (slice8; segments: 4)  (cost=4.16..8.83 rows=6 width=34)
                                                                         ->  Hash Join  (cost=4.16..8.58 rows=2 width=34)
                                                                               Hash Cond: public.nation.n_regionkey = public.region.r_regionkey
                                                                               ->  Append-only Columnar Scan on nation  (cost=0.00..4.25 rows=7 width=42)
                                                                               ->  Hash  (cost=4.11..4.11 rows=2 width=8)
                                                                                     ->  Broadcast Motion 4:4  (slice7; segments: 4)  (cost=0.00..4.11 rows=2 width=8)
                                                                                           ->  Append-only Columnar Scan on region  (cost=0.00..4.06 rows=1 width=8)
                                                                                                 Filter: r_name = 'AFRICA'::bpchar
     Settings:  enable_nestloop=off
     Optimizer status: legacy query optimizer
    (60 rows)

    下面对于查询分析中的一些参数做一些说明:

    slice:Greenplum在实现分布式执行计划的时候,需要将SQL拆分成多个切片,每个slice是单裤执行的一部分SQL,每一个广播或者重分布会产生一个切片,每一个切片在每一个数据结点上都会对应的发起一个进程来处理该slice负责的数据,上一层负责该slice的进程会读取下级slice广播或重分布的数据,之后进行相应的计算。

    segment:这里使用的是1个mdw、2个sdw,每个sdw中设置两个primary(greenplum安装时gpinitsystem使用的文件中设置),所以看到的segment是4。

    cost:数据库自定义的消耗单位,通过统计信息来估计SQL消耗。(查询分析是根据analyze的固执生成的,生成之后按照这个查询计划执行,执行过程中analyze是不会变的。所以如果估值和真是情况差别较大,就会影响查询计划的生成。)

    rows:根据统计信息估计SQL返回结果集的行数。

    width:返回结果集每一行的长度,这个长度值是根据pg_statistic表中的统计信息来计算的。

    6.对查询计划的结果进行分析

    1.逻辑架构图

    根据这个查询分析,画出查询的逻辑架构图:

    2.对上图做一些补充

    上图中我只画出了一个节点的逻辑架构,并没有表现出广播和重分布,下面用例子说明广播和重分布

    上面这个图是两个节点数据重分布的例子,这个图要完成的任务是两个表的Hash Join,由于某种原因(之后会讲述到)要将其中一个表的数据重分布到其他结点上去,以完成连接的任务。橘色部分是slice1,绿色部分是slice2,slice1中的表重分布之后到了slice2中,在slice2中做Hash Join,对连接之后的结果收集到master上。下图说明重分布时数据在slice之间传输的过程

     所以这也是为什么在逻辑图中,redistribution和broadcast的椭圆处于两个slice的交界处,并且同时属于两个slice。 

    3.能够产生slice的操作是redistribution、broadcast和gather

      1.gather、broadcast和redistribution的介绍

    gather:聚合,在master上讲子节点所有的数据聚合起来。一般的聚合规则是哪一个子节点的数据先返回到master上就将该节点的数据先放在master上。

    broadcast:广播,发生在两表关联的时候。将每个节点上的某个表的数据全部发送到所有节点,这样每个节点都相当于有全量数据。一般,小表的时候采用广播的方法。(注:的是不论大表还是小表,最初都是分散在所有子节点上的)

    redistribution:重分布,发生在两表关联的时候和group by的时候。当不满足广播的条件或者代价太大的时候,选择重分布,即按照新的分布键将各个节点上的数据重新打散到各个节点。

    下面着重介绍broadcast和redistribution

      2. join的时候的广播和重分布

    两表连接的时候可能广播可能重分布那么什么时候使用广播,什么时候使用重分布呢?在Query中使用到的都是Hash Join,所以我们暂时只讨论这种情况下的广播和重分布。

    通俗的讲,两表连接,如果是其中一个是小表,则将其广播,因为小表广播代价不会很大;如果两个表都是大表可能要重分布。分三种情况讨论:

    默认情况,我们认为主键id即为分布键

    ①select * from A,B where A.id = B.id     分布键就是关联键,两表可以在本结点直接连接

    ②select * from A,B where A.id = B.id2    A的分布键就是关联键,B的分布键不是关联键。所以不能将A重分布,有两种解决方案:

    a.将A广播(如果A是小表)

    b.将B重分布(如果A是大表)

    最终权衡取代价最小的方案

    ③select * from A,B where A.id2 = B.id2     A和B的分布键都不是关联键。

    a.将A或B都按照id2重分布

    b.将min(A,B)广播(如果较小的表是小表)

    最终权衡取代价最小的方案

    这里只讲述了Hash Join的情况,除此之外还有left join和full outer join,需要了解在书中135页有详解。

      3.group by时候的重分布

    在group by的时候也可能会产生重分布,下面介绍一下group by时重分布的原理:

    group by时时先在本机上进行一个group by,然后重分布,用group by时使用的字段作为分布键重分布,重分布之后再做一次group by,所以group by操作在分布式的环境下其实是做两次group by的。书上119页图有例子说明group by的数据重分布情况。

    4.分析Query2的查询计划

    下面按照每个切片的方式分析Query2的查询计划。

    slice1~4,包括slice10左分支的部分,是Query中的子查询部分;

    slice5~9是父查询中where子句中除了ps_supplycost = (子查询)的部分;

    slice10是ps_supplycost = (子查询)和父查询中的order by和limit。

    ①slice1和slice2:region表和nation表的hash join,数据库连接图可以看出应该属于上述第二种情况region.id=nation.id2,这里采用将region广播,说明相比重分布nation,广播region的代价更小;hash join的时候将region表hash,说明region表是个小表,这与广播region代价更小相吻合。

    ②slice3:hash join的原理同①

    ③slice4:hash join的原理同①,三次hash join实现了四个表的连接。之后做了聚合操作,对应子查询中min的操作,min的时候需要使用到group by,根据上面的介绍,知道group by在分布式环境下其实是做两次的,中间是一次重分布,这里可以在slice4和slice10的左子树看到有两次hashAggregate。

    ④slice5和slice6:hash join的原理同①

    ⑤slice7和slice8:hash join的同①

    ⑥slice9:左子树的hash join与slice3中的一样,不同的是连接之后,slice3进行了广播,slice9是进行了hash映射,造成这种差别的原因是这个连接表将要连接的表不同:

    slice3中hash join的中间表与partsupp连接,是hash join中的第二种情况,中间表.id=partsupp.id2,由于中间表是小表,所以将中间表广播;

    slice9中hash join的中间表1与slice6的中间表2连接,是hash join中的第二种情况,中间表1.id=中间表.id2,虽然中间表1是小表,但是没有广播中间表1,而是将中间表2重分布,因为中间表1广播到4个节点的代价总和大于将中间表2重分布。 

    ⑦slice10,hash join之后,对每个子节点的数据按照四个键值排序,每个节点舍弃掉一部分数据(排序比较靠后,不可能包含在最后的limit结果中),将排序靠前的数据输出给master,这个过程叫做gather,使用排序时的四个键值作为merge key

    取排序之后的一部分(这部分一定包含最终结果)输出给master,另一部分舍弃掉,不输出给master。master得到最终聚合的数据,再进行一次limit操作,这次limit得到的是query需要的100条数据。

    以上就是对Query2的查询计划结果的分析,还有一些问题尚未弄清楚:

    1.关联键是否默认是主键?是(书上201页)

    2.列存储是否有主键,是否有完整性约束?应该是有的,因为列存储取到相应属性之后还要将他们组合成行表。

    3.在查询计划的结果中,为什么query中的min(ps_supplycost)体现在查询计划中是group by(ps_partkey)而非group by(ps_supplycost)

    4.书上讲的hashAggregate和groupAggregate的原理不是很清楚

    5.slice10中的hash join的hash key不明白

    6.order by多个键值是怎么排序的

    心得:

    1.觉得画图写东西有点浪费时间,但是发现不画出来写出来,其实有些东西理解的不够,或者有偏差。记录下来有助于深入理解知识

    2.写东西相当于对知识体系的一次整合,没整理的时候,所有东西在大脑不是混沌的,真理之后知识变得逻辑、有序。

  • 相关阅读:
    【java基础】(5)静态绑定和动态绑定
    Java的接口和抽象类
    【java基础】(3)Java继承内存分配
    【java基础】(2)Java父类与子类的 内存引用讲解
    [ECMAScript 6]学习小结之---模块
    CDN 内容分发网络
    移动前端开发之viewport的深入理解
    px em rem
    https 结合使用 对称加密和非对称加密
    java Queue中 remove/poll, add/offer, element/peek区别
  • 原文地址:https://www.cnblogs.com/renlipeng/p/5689786.html
Copyright © 2020-2023  润新知