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.寫東西相當於對知識體系的一次整合,沒整理的時候,所有東西在大腦不是混沌的,真理之后知識變得邏輯、有序。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM