ppg_fdw:如何使用pgsql构建mpp数据仓库(五)

     子查询的处理
   说实话,本人在没看PG之前,一直看mysql的文档里面讲子查询,什么From、drived子查询啥的,一直晕乎乎的。但是在看了PG代码之后,终于感觉略微明白了点。
    具体说来,SQL里面,出现在rtable里面的子查询才可以称为是subquery,而出现在过滤条件(例如expression)中的被称为sublink。
    例如:
   
    select count(t1.a), tt.b from t1, (select t2.b, t2.c from t2) as tt
    where t1.a in (select d from t3) and t1.a = tt.c
    group by tt.b
    having count(t1.a) < (select max(t3.a) from t3) 
    

    很明显,select t2.b from t2是subquery,而select c from t3 select max(t3.a) from t3都是sublink。
    对于这个实例,PG会先将jointree中含有的sublink转成左半连接的subquery,生成一个新的jointree(见pull_up_sublink),然后将jointree中简单的subquery提上来(pull_up_subqueries)如是上面的SQL会被改写成如下的SQL:
   
    select count(t1.a), t2.b from (t1, t2,) semi joi t3 on ( t1.a =  t3.d ))  
    where  t1.a = t2.c
    group by t2.b
    having count(t1.a) < (select max(t3.a) from t3) 
    

    注意 semi join实际上是数据库内部的概念,不是一个标准token。被pullup的sublink一般都是简单的SQL, 而且是exist in 或是any条件中的,具体见代码。而对于在jointree中的不能pullup的sublink以及having语句中sublink,实际上是在pull_up_sublink和pull_up_subqueries之后处理的(见preprocess_expression)。这些sublink,分为两种,一种是的独立sublink,这类sublink的SQL中不引用上层的Query中的变量(var);另外一种就是相关的sublink,引用了上层的var。
   对于独立的sublink, 查询优化器会为之生成initplan,initplan只会被执行一次,但是存储了结果,having 条件通过变量编号来引用这个initplan。
   如果是关联的sublink,查询优化器会为之生成subplan。外围的having对应的expression在执行时(实际就是一个agg的filter),会传入所引用的var的实际值,然后执行这个subplan,来获得expression的结果。
   好了,在研究了单机的PG如何处理子查询之后,下面我们将描述ppg_fdw如何处理子查询的SQL.
   首先,对于出现在jointree中的sublink,ppg_planner直接调用PG的接口(pull_up_sublink,pull_up_subqueries)来改写Query,最终变成不存在子查询的SQL。例如,对于这条SQL实例:
  
   select
        o_orderpriority,
        count(*) as order_count
   from
        orders
   where
        o_orderdate >= date '1993-07-01'
        and o_orderdate < date '1993-07-01' + interval '3' month
        and exists (
                select
                        *
                from
                        lineitem
                where
                        l_orderkey = o_orderkey
                        and l_commitdate < l_receiptdate
        )
   group by
        o_orderpriority
   order by
        o_orderpriority
   LIMIT 1;
   

   ppg_planner会将sublink消灭之后只剩下semi join,然后采用和前面处理join和agg的方式生成全局最优plan以及下推的子计划(略去)。
  而对于下面的SQL,实际上就需要initplan了:
 
  select
        ps_partkey,
        sum(ps_supplycost * ps_availqty) as value
  from
        partsupp,
        supplier,
        nation
  where
        ps_suppkey = s_suppkey
        and s_nationkey = n_nationkey
        and n_name = 'PERU'
  group by
        ps_partkey having
                sum(ps_supplycost * ps_availqty) > (
                        select
                                sum(ps_supplycost * ps_availqty) * 0.0001000000
                        from
                                partsupp,
                                supplier,
                                nation
                        where
                                ps_suppkey = s_suppkey
                                and s_nationkey = n_nationkey
                                and n_name = 'PERU'
                )
  order by
        value desc
  LIMIT 1;

   因为
   select
                                sum(ps_supplycost * ps_availqty) * 0.0001000000
                        from
                                partsupp,
                                supplier,
                                nation
                        where
                                ps_suppkey = s_suppkey
                                and s_nationkey = n_nationkey
                                and n_name = 'PERU'
   
是一个独立的SQL,因此ppg_planner会为之生成一个plan,然后将这个plan的编号塞入上层的havingClause的expression中被引用。
   而对于下面这个例子,则彻底需要subplan:
  
   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 = 41
        and p_type like '%TIN'
        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;
   

   主要是havingcluase的子查询是一个相关的子查询,引用了part表的p_partkey字段。
   对于出现在fromclause中的subquery, ppg_planner会尽力在pull_subqueries中消灭之,一般无法pullup的subquery含有agg。如果存在这样的pullup的subquery,那么就要通过ppg_planner来生成子plan。
   例如对于这个SQL实例:
  
   select
        supp_nation,
        cust_nation,
        l_year,
        sum(volume) as revenue
  from
        (
                select
                        n1.n_name as supp_nation,
                        n2.n_name as cust_nation,
                        extract(year from l_shipdate) as l_year,
                        l_extendedprice * (1 - l_discount) as volume
                from
                        supplier,
                        lineitem,
                        orders,
                        customer,
                        nation n1,
                        nation n2
                where
                        s_suppkey = l_suppkey
                        and o_orderkey = l_orderkey
                        and c_custkey = o_custkey
                        and s_nationkey = n1.n_nationkey
                        and c_nationkey = n2.n_nationkey
                        and (
                                (n1.n_name = 'KENYA' and n2.n_name = 'VIETNAM')
                                or (n1.n_name = 'VIETNAM' and n2.n_name = 'KENYA')
                        )
                        and l_shipdate between date '1995-01-01' and date '1996-12-31'
        ) as shipping
   group by
        supp_nation,
        cust_nation,
        l_year
   order by
        supp_nation,
        cust_nation,
        l_year
   LIMIT 1;
   

   实际上可以将fromclause里面那个子查询pullup,最终变成一个没有子查询的SQL,然后生成全局plan。可以这样处理的TPCH的SQL实例实际上还有若干个,这里就不列举了。
   而对于下面这个SQL,则需要处理子查询了。
  
   select
        c_count,
        count(*) as custdist
   from
        (
                select
                        c_custkey,
                        count(o_orderkey)
                from
                        customer left outer join orders on
                                c_custkey = o_custkey
                                and o_comment not like '%pending%accounts%'
                group by
                        c_custkey
        ) as c_orders (c_custkey, c_count)
    group by
        c_count
    order by
        custdist desc,
        c_count desc
    LIMIT 1;

   
ppg_planner会为这条SQL中的子查询首先生成一个plan(为了方便,我们称为子plan),然后为上层的SQL生成一个plan(我们称为父plan)。逻辑上全局plan可以如下表示:
               -------------------------
               -       (limit)        -   父plan
               -           |           -
               -        (sort)       -
               -           |           -
               -        (agg)        -
               -           |           -
               -     (foreign_scan)    -
               -           |           -
               --------------------------
                           |   
               --------------------------
               -        (agg)         -
               -           |            -
               -      (foreign_scan)    -      子plan
               --------------------------
   (由于这个iteye实在垃圾,上传图片老失败,只能这样讲究了)

    父plan的叶子节点是foreign_scan,而这个foreign_scan的输入参数包括要下推的SQL以及子plan的id。
    这个plan执行时,会分为两步:一,在executor执行父plan到foreign_scan时,会根据参数,先执行一个建立临时表的语句,然后执行子plan,从子plan获得结果插入临时表;二,一旦完成临时表数据的插入之后,执行父plan,从foreign_scan到二次聚集agg到sort最后到limit。
     对于这种子查询处理,需要根据到子查询生成数据量来决定数据的分布方式:如果子查询的数据量小,可以认为是新生成了一个dimension表,并且这时的rangetable就是这个子查询,就像上面的例子,那么可以考虑将子查询的结果在DQP的本地进行处理,那么需要在上图父plan的foreign_scan上面插入一个subquery,省去了在OP上建立临时表和和插入数据的开销;如果子查询的数据量很小,并且还要和rangetable做join,这就要考虑rangetable其他表中是否含有fact表了,如果含有fact表,那么需要将这个新的临时表做全分布了,在每个OP上都建立临时表并都有一个份数据,如果做join的其他全是dimension表,那么这个SQL就演变成了对只含有dimension表的操作了,需要选择一个负载低的OP建立临时表插入数据后,将SQL转发给其处理;如果子查询的数据量比较大,那么认为新产生了一个fact表,需要根据join的字段做重分布,然后下推SQL。具体怎么处理关键需要建立一个统一的模型来估算代价,可能需要向PG的pg_catalog加入一个新的meta表,这个需要进一步研究,To be Done。

猜你喜欢

转载自scarbrofair.iteye.com/blog/2162573