全部產品
Search
文件中心

PolarDB:列存索引中GroupJoin運算元的實現

更新時間:Mar 13, 2025

本文介紹了PolarDB IMCI中GroupJoin運算元的一些限制條件和實現方式,以及其他資料的一些相關實現。閱讀本文前請先瞭解基礎的HASH JOIN與HASH GROUP BY演算法。

背景

SELECT
  key1,
  SUM(sales) as total_sales
FROM
  fact_table LEFT JOIN dimension_table ON fact_table.key1 = dimension_table.key1
GROUP BY
  fact_table.key1
ORDER BY
  total_sales
LIMIT 100;

PolarDB IMCI中,類似以上查詢語句的執行計畫是先執行一遍HASH JOIN,再執行HASH GROUP BY key1。在這兩個操作中,都會使用key1建立雜湊表(注意fact_table.key1 = dimension_table.key1),執行計畫說明如下:

  1. HASH JOIN:使用dimension_table.key1建雜湊表,使用fact_table.key1查雜湊表並輸出資料;

  2. HASH GROUP BY:使用fact_table.key1建雜湊表,寫入雜湊表的過程中做彙總運算。

從執行效率的角度來看,這兩個操作可以合并成一個,使用dimension_table.key1建雜湊表以及做彙總運算,使用fact_table.key1查雜湊表以及做彙總運算因此節省了使用fact_table.key1建雜湊表的時間。這種將HASH JOIN與HASH GROUP BY兩個運算元合并成一個的操作,就是GroupJoin。

從執行效率的角度來看,將這兩個操作合并成一個操作,不僅可以減少一次建雜湊表的操作,還可以減小中間結果大小。因為JOIN是一個可能使“結果集膨脹”的運算,一張表的一行可能會匹配上另一張表的多行,最壞情況下便是笛卡兒積:N行的表與M行的表JOIN的結果最大可能是N×M的結果集。因此在HASH JOIN+HASH GROUP BY的執行方式中,一張N行的雜湊表可能會輸出N×M×S行結果(S代表selectivity,0≤S≤1),然後在HASH GROUP BY的grouping操作中再被彙總成一張新的雜湊表,這會造成資源浪費。即使是上面例子中“事實表”(大表,大小為M)與“維度資料表”(小表,大小為N)的LEFT OUTER JOIN,且key1都是unique key,也是從一張N行的雜湊表, 經過HASH JOIN輸出M行結果,然後彙總成M行的雜湊表。相對而言,GroupJoin只需要在N行的雜湊表中完成join&aggr運算,不僅中間結果變少了,同時記憶體佔用也變小了。

基於以上考慮,PolarDB MySQL版PolarDB IMCI中增加了GroupJoin運算元。

演算法設計

概述

IMCI裡的GroupJoin實現,是HashJoin與HashGroupby兩個運算元的融合:

  1. 先使用左表(小表)建立雜湊表,涉及左表的aggr函數會在建雜湊表的時候直接運算掉。這個過程與對左表彙總(i.e., HashGroupby left_table)的操作是相同的。

  2. 使用右表(大表)查雜湊表,查詢命中則在hash table entry上運算涉及右表的aggr函數,否則丟棄或者直接輸出。

以上介紹了IMCI GroupJoin演算法的基本思路,下文會對演算法進行詳細的描述以及介紹簡化的方法。

限制條件

出於實現的複雜度考慮,相對於理論上最完備的GroupJoin實現,PolarDB MySQL版做了如下幾點限制:

  1. group by key要完全符合某一邊,且只能是某一邊的join key,雖然某些情況下join key的一部分,也能唯一定義這個key(i.e., functional dependency);

  2. RIGHT JOIN、GROUP BY RIGHT的情境,要求right keys是unique keys。否則可能會轉成LEFT JOIN、GROUP BY LEFT的方式,或者不使用GroupJoin;

  3. 任意一個aggr函數只能單獨引用左表,或者單獨引用右表;如果原GROUP BY運算元中的aggr函數同時引用了左右兩個表(e.g., SUN(t1.a+t2.a)),則不適用GroupJoin。

演算法

INNER JOIN/GROUP BY LEFT

此情境如下SQL所示:

l_table INNER JOIN r_table
ON l_table.key1 = r_table.key1
GROUP BY l_table.key1
說明

假設實際執行順序與SQL描述一樣,且Join過程中不會動態換邊。

  1. 使用左表建雜湊表,並且建立雜湊表的過程中直接運算涉及左表的aggr函數;涉及右表的aggr函數,對應設一個“repeat count”,這等同於一個hash table entry對應的payload的數量;

  2. 在join過程中,使用右表查雜湊表,如果不匹配,則右表的行直接被丟棄;如果匹配,左表的aggr context的“repeat count”會增加1,右表的aggr函數直接進行運算;

  3. join完成後,只輸出曾經被匹配上的hash table entry的aggr結果,沒有被匹配上的hash table entry全部忽略;

  4. 輸出aggr結果時,要考慮“repeat count”,例如如果一個SUM(expr)的結果是200,“repeat count”是5,則最終結果是1000。

INNER JOIN/GROUP BY RIGHT

此情境如下SQL所示:

l_table INNER JOIN r_table
ON l_table.key1 = r_table.key1
GROUP BY r_table.key1

考慮到l_table.key1=r_table.key1,這種情況被歸到“INNER JOIN, GROUP BY LEFT”裡。

LEFT OUTER JOIN/GROUP BY LEFT

此情境如下SQL所示:

l_table LEFT OUTER JOIN r_table
ON l_table.key1 = r_table.key1
GROUP BY l_table.key1
  1. 使用左表建雜湊表,建雜湊表的過程中運算左表的aggr函數;涉及右表的aggr函數,對應設一個“repeat count”;

  2. 在join過程中,使用右表查雜湊表,如果不匹配,則右表的行直接被丟棄;如果匹配,左表的aggr context的“repeat count”會增加1,右表的aggr函數直接進行運算;

  3. 與INNER JOIN不同,此情境中join完成後,被匹配上的hash table entry的aggr結果直接輸出,沒有被匹配上的每個hash table entry單獨成為一個GROUP,對應的右表的aggr函數的輸入都是NULL。

LEFT OUTER JOIN/GROUP BY RIGHT

此情境如下SQL所示:

l_table LEFT OUTER JOIN r_table
ON l_table.key1 = r_table.key1
GROUP BY r_table.key1
  1. 使用左表建雜湊表,建雜湊表的過程中運算左表的aggr函數;涉及右表的aggr函數,對應設一個 “repeat count”;

  2. 在join過程中,使用右表查雜湊表,如果不匹配,則右表的行直接被丟棄;如果匹配,左表的aggr context的“repeat count”會增加1,右表的aggr函數直接進行運算;

  3. 與其他情境不同,此情境中join完成後,被匹配上的hash table entry的aggr結果直接輸出,沒有被匹配上的所有hash table entry成為一個GROUP,對應的右表的aggr函數的輸入都是NULL。

RIGHT OUTER JOIN/GROUP BY LEFT

此情境如下SQL所示:

l_table RIGHT OUTER JOIN r_table
ON l_table.key1 = r_table.key1
GROUP BY l_table.key1
  1. 使用左表建雜湊表,建立雜湊表的過程中運算左表的aggr函數;涉及右表的aggr函數,對應設一個“repeat count”;

  2. 與其他情境不同,此情境在join過程中,使用右表查雜湊表,如果匹配,左表的aggr context的“repeat count”會增加1,右表的aggr函數直接進行運算;如果不匹配,則右表的所有不匹配的行成為一個GROUP,對應的左表的aggr函數結果都是NULL;

  3. 與其他情境不同,此情境在join完成後,被匹配上的hash table entry的aggr結果直接輸出,沒有被匹配上的所有hash table entry全都忽略。

RIGHT OUTER JOIN/GROUP BY RIGHT

此情境如下SQL所示:

l_table RIGHT OUTER JOIN r_table
ON l_table.key1 = r_table.key1
GROUP BY r_table.key1

限制條件

要求r_table.key1必須是distinct的,否則這種join是不合法的;如果不能確定r_table.key1是distinct的,則需要在最佳化器裡將這種join+groupby轉成LEFT OUTER JOIN、GROUP BY LEFT。

執行步驟

  1. 使用左表建雜湊表,建雜湊表的過程中運算左表的aggr函數;涉及右表的aggr函數,對應設一個“repeat count”;

  2. 與其他情境不同,此情境在join過程中,使用右表查雜湊表,如果匹配,直接輸出左右表的aggr結果;如果不匹配,也輸出aggr結果,此時左表的aggr結果都是NULL;

  3. 與其他情境不同,此情境在join完成後,GroupJoin即完成,不需要處理任何hash table entry。

運行時落盤(spilling)處理

GroupJoin的落盤處理,類似於partition-style的HashJoin&HashGroupby的落盤處理,方法如下:

  1. GroupJoin的整體演算法採用分區(partition)的方式;

  2. 使用左表構建雜湊表時,記憶體中的partition,構建hash table的演算法與演算法一節描述一致;

  3. 使用左表構建雜湊表時,不在記憶體中的partition,刷到磁碟中對應的臨時檔案,後續新寫入這個partition 的資料也會直接刷到磁碟中對應的臨時檔案;落盤的partition會建立一個bloomfilter,方便後續尋找的時候快速過濾掉不可能匹配的右表資料;

  4. 完成左表的雜湊表構建後,使用右表資料查雜湊表:

    1. 在查雜湊表的過程中,如果對應partition在記憶體中,則如演算法中的處理方式一樣;如果對應的partition不在記憶體中,則先查bloomfilter,如果不命中bloomfilter則直接丟棄或者直接輸出,否則刷入該partition對應的臨時檔案中。

    2. 記憶體中的partition完全處理完畢後,逐個處理磁碟中的partition,此時假設至少一個partition時能放入磁碟中的,不需要再切分一個partition;處理演算法與演算法中的處理方式一樣。

相關實現

2011年的一篇論文Accelerating Queries with Group-By and Join by Groupjoin(簡稱 paper_1)從理論角度闡述了GroupJoin在不同查詢計劃中的可行性,但是不涉及太多實現的細節。論文描述了GroupJoin運算時的約束,以及適用GroupJoin的情境,比如不同aggr函數如何處理等,描述得比較抽象,整體可讀性不是很好。

2021年有一篇論文A Practical Approach to Groupjoin and Nested Aggregates (簡稱 paper_2)描述了如何在(記憶體)資料庫中高效地實現GroupJoin運算元。這篇論文的幾個重點是:

1. 解關聯子查詢的演算法中使用GroupJoin

image.png

在解決“關聯項上方有 GROUP BY”這種關聯子查詢時,有一種方式是引入“MagicSet”操作(也就是 table distinct)並在上方增加一個JOIN+GROUP BY,從而完成子查詢的去關聯。這種模樣的執行計畫,恰好符合GroupJoin的適用情境。由於IMCI目前也使用了類似的解關聯演算法,但是目前暫時不能產生此類shared children的執行計畫。

2. Eager aggregation

簡單來說,就是在hash build的過程中,把hash build那一邊的aggr函數直接計算掉,而不是為每個hash table entry保留完整的payload,最後再運算aggr函數。這與IMCI的實現思路是一樣的。

3. 使用memoizing的方式解決並發查雜湊表做彙總運算時的衝突

舉個極端的例子:hash probe的過程中,所有資料都命中hash table的同一個entry,因此要在此entry進行彙總運算(比如SUM(2 * col)),因此需要使用同一個“aggr context運行aggr函數;例如,對於 SUM() 彙總函式來說,就是不斷使用同一個sum_value進行加法操作。即使是原子變數的加法操作,遇到contention也是有效能問題的,更何況是通用的彙總函式運算。這篇論文的思路是為所有“沒有搶到entry所有權”的線程建立各自的local hash table進行運算,對於每個entry,利用CAS指令設定一個owner thread id,除了owner thread外,後續到來的thread都使用自己的local hash table進行運算,最後再將這些local hash table合并到global hash table裡。

4. 並不是所有情境都適用於GroupJoin

有些情境更適用於Join+Groupby。舉個例子,假設hash build一邊的selectivity很低,hash probe完成後,這一邊的大多數行都不會被選中,那麼就會遇到了一個兩難的境地了:

  • 如果在hash build的時候,同時把這一邊的彙總運算做掉(i.e.,eager aggregation),則不需要保留hash table的payload,節省了記憶體,但是因為join的selectivity很低,大多數行最後都不會被選中,因此這些提前運算的agg運算都浪費掉了;

  • 如果不提前做aggr運算,則需要多花一些記憶體;但是在join的selectivity很高(大多數行都被選中)的情況下,這些記憶體本可以通過提前進行的aggr運算節省掉。

所以,如果join的selectivity很低,那麼更好的方式或許是:join完後,得到非常少的group,然後在HashGroupby中做局部性很強的彙總操作。因此,這篇論文提出了幾種情境下不同的實現。而為了知道一個查詢適用於哪種情境,需要最佳化器提供selectivity和cardinality的估算,因此論文後面配套提供了一些最佳化器裡用到的估算方法。

從實現者的角度看來,上面所說的“兩難的境地”其實並不是太大的問題,這是因為:

  • PolarDB IMCI幾乎總是用小的一邊(小表)建雜湊表;

  • 即使JOIN的selectivity很低,使用eager aggregation提前運算彙總函式的策略,雖然浪費了針對一個小表的運算時間,但是無論如何也節省了記憶體,並不是一無所獲;這種情況下,相對於HashJoin+HashGroupby,也算是時間換空間。

在IMCI的實現裡面,除了上文說的RIGHT JOIN+GROUP BY RIGHT情境,PolarDB IMCI幾乎總是認為GroupJoin的執行效率是優於HashJoin+HashGroupby。

從作者以及論文裡面的測試情況來看,上述的兩篇論文應該都來自慕尼黑大學的hyper資料庫團隊。除hyper外,目前還沒有見到其他資料庫實現GroupJoin運算元,但是應該有“shared hash table”操作的其他實現方式,後續再進一步討論。

GroupJoin在TPCH中的應用

TPCH是一個常用的測試一個AP系統的分析查詢能力的benchmark。在TPCH的22條查詢中,有不少都是適用GroupJoin運算元的。不過,除了TPCH Q13,其他的查詢語句都需要經過一定改造才能適用GroupJoin運算元。

Q13

TPCH Q13,可以直接適用GroupJoin運算元:

select
    c_count,
    count(*) as custdist
from
    (
        select
            c_custkey,
            count(o_orderkey) as c_count
        from
            customer
            left outer join orders on c_custkey = o_custkey
            and o_comment not like '%pending%deposits%'
        group by
            c_custkey
    ) c_orders
group by
    c_count
order by
    custdist desc,
    c_count desc;
  • 在IMCI中,如果不使用GroupJoin,則執行計畫如下:

    image.png

  • 如果使用GroupJoin,執行計畫如下:

    image.png

Q3

對TPCH的Q3而言,GroupJoin的最佳化需要經過一系列等價變換:

select
    l_orderkey,
    sum(l_extendedprice * (1 - l_discount)) as revenue,
    o_orderdate,
    o_shippriority
from
    customer,
    orders,
    lineitem
where
    c_mktsegment = 'BUILDING'
    and c_custkey = o_custkey
    and l_orderkey = o_orderkey
    and o_orderdate < date '1995-03-15'
    and l_shipdate > date '1995-03-15'
group by
    l_orderkey,
    o_orderdate,
    o_shippriority
order by
    revenue desc,
    o_orderdate
limit
    10;

Q3的一種可行的執行計畫如下(IMCI中的執行計畫): DERKEY,TEMPTABLE

image.png

由於此SQL的grouping keys是l_orderkey、o_orderdate、o_shippriority,與任何一個join keys都不相同,因此並不能直接適用GroupJoin。通過一些等價推導,可得出以下結論:

  1. 由於lineitem與orders表的join predicate是l_orderkey=o_orderkey,而且是INNER JOIN,因此可以判斷出,這個join的結果集裡面,l_orderkey=o_orderkey;

  2. 由於l_orderkey=o_orderkey,因此 GROUP BY l_orderkey、o_orderdate、o_shippriority 等價於 GROUP BY o_orderkey、o_orderdate、o_shippriority;

  3. 由於o_orderkey是orders表的PRIMARY KEY,因此每一個o_orderkey都能直接確定唯一的o_orderdate和o_shippriority (i.e.,o_orderdate and o_shippriority functionally depend on o_orderkey);

  4. 由於o_orderkey能唯一確定o_orderdate和o_shippriority,因此GROUP BY o_orderkey、o_orderdate、o_shippriority等價於GROUP BY o_orderkey;

由上面的推導,可以將Q3的group by clause等價變換成GROUP BY o_orderkey,如此可適用於GroupJoin了:KEY,TEMPTABLE3.SUM(LINETTEM.EXTENDEDPRTCE*1.00-LUNETEM._DLSCOL

image.png

這種“functional dependency”的推導,對最佳化器有一定要求。目前MySQL最佳化器中,實現了部分functional dependency的推導,但是依然無法推匯出上面的GROUP BY o_orderkey變換。經過嘗試,發現SQL SERVER是可以推匯出GROUP BY o_orderkey變換的,這方面有比較完備的理論,但是IMCI目前在這方面還沒有完全實現。在TPCH裡面,Q3/Q4/Q10/Q13/Q18/Q20/Q21都有這種特徵,如果能做這種等價推導,將可以縮短GROUP BY的grouping keys,提高彙總操作的速度。

Q10

TPCH的Q10也不能直接適用GroupJoin:

select
    c_custkey,
    c_name,
    sum(l_extendedprice * (1 - l_discount)) as revenue,
    c_acctbal,
    n_name,
    c_address,
    c_phone,
    c_comment
from
    customer,
    orders,
    lineitem,
    nation
where
    c_custkey = o_custkey
    and l_orderkey = o_orderkey
    and o_orderdate >= date '1993-10-01'
    and o_orderdate < date '1993-10-01' + interval '3' month
    and l_returnflag = 'R'
    and c_nationkey = n_nationkey
group by
    c_custkey,
    c_name,
    c_acctbal,
    c_phone,
    n_name,
    c_address,
    c_comment
order by
    revenue desc
limit
    20;

如果要使用GroupJoin,需要做以下兩個變換:

  1. 通過等價變換把grouping keys變成c_custkey(customer表的PRIMARY KEY),這個變換與上文的Q3類似;

  2. Join order要調整,使得customer表的JOIN在最外層。

其中1總是有益的,但是2中join order的調整,不一定是有益的。

Q17

TPCH的Q17包含一條關聯子查詢:

select
    sum(l_extendedprice) / 7.0 as avg_yearly
from
    lineitem,
    part
where
    p_partkey = l_partkey
    and p_brand = 'Brand#44'
    and p_container = 'WRAP PKG'
    and l_quantity < (
        select
            0.2 * avg(l_quantity)
        from
            lineitem
        where
            l_partkey = p_partkey
    );

其去關聯的方式有幾種,目前IMCI針對scalar aggr實現的兩種去關聯演算法得到的執行計畫分別是:

image.pngRE

image.png

這些執行計畫都不適用GroupJoin運算元。如果採用MagicSet運算元的去關聯方式,在移除MagicSet運算元之前,會得到一個適合GroupJoin的中間態:

image.png

也就是paper_2中所描述的過程: NERALNESTING:DEEORRELATIONOFDEPENDENTSUB-

image.png

因此可以適用GroupJoin。目前IMCI部分實現了採用MagicSet運算元的去關聯方式,但是不會產生hared children的執行計畫,因此IMCI裡面無法對TPCH Q17適用GroupJoin。

Q18

TPCH Q18也是可以適用GroupJoin的,不過依然要利用等價變換轉換執行計畫,才能得到適用GroupJoin的執行計畫。為了方便描述,不失一般性,此處把Q18裡的IN子查詢以及最後的ORDER BY去掉:

select
    c_name,
    c_custkey,
    o_orderkey,
    o_orderdate,
    o_totalprice,
    sum(l_quantity)
from
    customer,
    orders,
    lineitem
where
    c_custkey = o_custkey
    and o_orderkey = l_orderkey
group by
    c_name,
    c_custkey,
    o_orderkey,
    o_orderdate,
    o_totalprice

對於這個查詢,做如下等價推導:

  1. 因為c_custkey是customer表的PRIMARY KEY,因此c_name可以由c_custkey唯一確定(functional dependency);同理o_orderkey是orders表的PRIMARY KEY,o_orderdate與o_totalprice都可以由o_orderkey唯一確定。因此,group by clause可以被等價轉換為GROUP BY c_custkey, o_orderkey;

  2. 由於customer表與orders表的join predicate是c_custkey=o_custkey,因此可以斷言,join的結果集中,c_custkey=o_custkey;

  3. 由於c_custkey=o_custkey,因此group by clause可以被等價轉換為GROUP BY o_custkey, o_orderkey;

  4. 由於o_orderkey唯一確定o_custkey (o_orderkey是orders表的主鍵),因此group by clause可以被等價改寫為GROUP BY o_orderkey。

經過以上等價推導,整個查詢可以被等價改成類似如下一個SQL:

select
    ANY_VALUE(c_name),
    ANY_VALUE(c_custkey),
    o_orderkey,
    ANY_VALUE(o_orderdate),
    ANY_VALUE(o_totalprice),
    sum(l_quantity)
from
    customer,
    orders,
    lineitem
where
    c_custkey = o_custkey
    and o_orderkey = l_orderkey
group by
    o_orderkey
  • 不帶GroupJoin的執行計畫

    image.png

  • 帶GroupJoin的執行計畫

    image.png

上面的等價推導,因為能減少GROUP BY的grouping keys的長度,因此針對常規的執行計畫,也是有用的。

Q20

TPCH Q20的關聯子查詢的pattern與Q17是類似的:採用MagicSet運算元的去關聯方式,在移除MagicSet運算元之前,會得到一個適合GroupJoin的中間態。

select
...
and ps_availqty > (
    select
        0.5 * sum(l_quantity) < ! --- scalar aggr --->
    from
        lineitem
    where
        l_partkey = ps_partkey         < ! --- 關聯項 1 --->
        and l_suppkey = ps_suppkey     < ! --- 關聯項 2 --->
        and l_shipdate >= '1993-01-01'
        and l_shipdate < date_add('1993-01-01', interval '1' year)
)

其他

按論文paper_1和paper_2所述,Q5/Q9/Q16/Q21這4條SQL都適用GroupJoin運算元,但是暫時還沒找到合適的轉換路徑;通過查詢hyper資料庫的執行計畫(https://hyper-db.de/interface.html#),它的最佳化器也沒有為這幾條SQL產生帶有GroupJoin的執行計畫。

相關實現

TPCH benchmark裡面的許多query都是JOIN+GROUP BY的模式,因此TPCH裡有不少的query都能通過GroupJoin最佳化掉。在論文paper_1裡,作者列出Q3/Q5/Q9/Q10/Q13/Q16/Q17/Q20/Q21這些query在使用GroupJoin與不使用GroupJoin兩種情況下的效能:

image.png

此處使用的是TPCH 1 GB的資料量。可以看出GroupJoin對最佳化TPCH類型query有一定的作用(總體時延1932ms降到1295ms)。

在論文paper_2中,則是分別列出了Q3/Q13/Q17/Q18這些query在使用GroupJoin與不使用GroupJoin幾種情況下的效能(TPCH 10 GB資料量):

image.png

圖中的幾組線條含義如下:

  1. 圖中 “seperate”這一組線條代表“分別做JOIN和GROUP BY”,也就是不使用GroupJoin;

  2. 圖中“eager”代表上文所說的“eager aggregation”這一最佳化;

  3. 圖中“memoizing”代表上文所說的“如何處理並發查雜湊表做aggr運算時的衝突”這一最佳化。 可以看到,在 Q3/Q13/Q17/Q18 這4條query中:

    1. "memoizing" 的方式幾乎總是與一般的HASH JOIN+HASH GROUP BY的方式有著類似的效能;

    2. eaegr aggregation的方式只在Q13這一條query中佔有優勢,其餘都不佔優勢。

按照圖中的資料可得:不同的處理方法,在不同情境中差別很大。因此這個資料呼應了作者提出的 “GroupJoin 的執行方式,需要最佳化器提供更準確的統計資訊,以選擇最優的執行方式”這一觀點,而不是無差別地選擇某一種GroupJoin演算法,甚至無差別地選擇使用GroupJoin。

雖然如此,對於這個結論,PolarDB有不同的觀點:

  1. 文章中使用tuples per second這個指標來衡量演算法的好壞,但是與PolarDB IMCI中得到的結論卻不太一樣。使用IMCI在並發度=32的情況下測試Q3/Q13/Q18這3條query的GroupJoin運算元的throughput(單位tuples/s),結果如下:

    Query

    HashJoin+HashGroupby

    GroupJoin

    Q3

    130 MB

    152 MB

    Q13

    11 MB

    33 MB

    Q18

    315 MB

    1 GB

    說明

    Q17在IMCI裡暫時無法使用GroupJoin。

    這個測試資料與上圖中的資料在量級上是相似的,但是每條query都稍有不同。也許是實現方式的不同,從PolarDB的測試資料中可以觀察到,除了上文說的right join+groupby right情況外,GroupJoin幾乎總是優於HashJoin+HashGroupby的。

  2. 對於上面3.a的結論,即“memoizing”的方式幾乎總是與一般的HASH JOIN+HASH GROUP BY的方式有著類似的效能,根據我們的觀察,TPCH的這幾條query只有非常少量的contention,因此memoizing的方式所用的local hash table等,在實際運行時基本不會用到,因此在這幾條query裡面,這個演算法得到的效能與HASH JOIN+HASH GROUP BY是類似的;論文裡引用這幾條query的效能作為對比,其實不說明問題PolarDB是通過直接加鎖的方式來測試回合時的contention的。

總結

效果來講,因為GroupJoin在運行時能避免的重複的工作,因此在某些情境能得到比較大的效能提升。這個效果已經在實際應用中得到驗證。因此從結果的角度,GroupJoin是值得實現的。

從通用性來講,GroupJoin並不通用。GroupJoin只適用於equal join+group by且要求grouping keys與任意一邊join keys相同,而且對aggr函數、實現方式等有諸多限制;這是一種特化,而隨之而來的是比較大的實現和維護代價。從開發的角度來說,應該花更大力氣去最佳化“通用路徑”,利用通用路徑的效能提升來達到最佳化SQL查詢效率的目的,而不是通過為某個情境尋求定製性的解法。因此從這個角度來說,GroupJoin不是一個好方法。

因此在實現的時候,應該做一定的裁剪或簡化,不追求在一個特化實現裡面實現最完備的功能,但是追求最常見情境的效用(效能)最大化。