Doris Join 優(yōu)化原理文檔詳解
Doris Join 優(yōu)化原理
Doris 支持兩種物理算子,一類是 Hash Join,另一類是 Nest Loop Join。
- Hash Join:在右表上根據(jù)等值 Join 列建立哈希表,左表流式的利用哈希表進(jìn)行 Join 計(jì)算,它的限制是只能適用于等值 Join。
- Nest Loop Join:通過兩個(gè) for 循環(huán),很直觀。然后它適用的場景就是不等值的 Join,例如:大于小于或者是需要求笛卡爾積的場景。它是一個(gè)通用的 Join 算子,但是性能表現(xiàn)差。
作為分布式的 MPP 數(shù)據(jù)庫, 在 Join 的過程中是需要進(jìn)行數(shù)據(jù)的 Shuffle。數(shù)據(jù)需要進(jìn)行拆分調(diào)度,才能保證最終的 Join 結(jié)果是正確的。舉個(gè)簡單的例子,假設(shè)關(guān)系S 和 R 進(jìn)行Join,N 表示參與 Join 計(jì)算的節(jié)點(diǎn)的數(shù)量;T 則表示關(guān)系的 Tuple 數(shù)目。
Doris Shuffle 方式
Doris 支持 4 種 Shuffle 方式
Broadcast Join
它要求把右表全量的數(shù)據(jù)都發(fā)送到左表上,即每一個(gè)參與 Join 的節(jié)點(diǎn),它都擁有右表全量的數(shù)據(jù),也就是 T(R)。
它適用的場景是比較通用的,同時(shí)能夠支持 Hash Join 和 Nest loop Join,它的網(wǎng)絡(luò)開銷 N * T(R)。
左表數(shù)據(jù)不移動(dòng),右表數(shù)據(jù)發(fā)送到左表數(shù)據(jù)的掃描節(jié)點(diǎn)。
Shuffle Join
當(dāng)進(jìn)行 Hash Join 時(shí)候,可以通過 Join 列計(jì)算對(duì)應(yīng)的 Hash 值,并進(jìn)行 Hash 分桶。
它的網(wǎng)絡(luò)開銷則是:T(R) + T(N),但它只能支持 Hash Join,因?yàn)樗歉鶕?jù) Join 的條件也去做計(jì)算分桶的。
左右表數(shù)據(jù)根據(jù)分區(qū),計(jì)算的記過發(fā)送到不同的分區(qū)節(jié)點(diǎn)上。
Bucket Shuffle Join
Doris 的表數(shù)據(jù)本身是通過 Hash 計(jì)算分桶的,所以就可以利用表本身的分桶列的性質(zhì)來進(jìn)行 Join 數(shù)據(jù)的 Shuffle。假如兩張表需要做 Join,并且 Join 列是左表的分桶列,那么左表的數(shù)據(jù)其實(shí)可以不用去移動(dòng)右表通過左表的數(shù)據(jù)分桶發(fā)送數(shù)據(jù)就可以完成 Join 的計(jì)算。
它的網(wǎng)絡(luò)開銷則是:T(R)相當(dāng)于只 Shuffle 右表的數(shù)據(jù)就可以了。
左表數(shù)據(jù)不移動(dòng),右表數(shù)據(jù)根據(jù)分區(qū)計(jì)算的結(jié)果發(fā)送到左表掃表的節(jié)點(diǎn)
Colocate
它與 Bucket Shuffle Join 相似,相當(dāng)于在數(shù)據(jù)導(dǎo)入的時(shí)候,根據(jù)預(yù)設(shè)的 Join 列的場景已經(jīng)做好了數(shù)據(jù)的 Shuffle。那么實(shí)際查詢的時(shí)候就可以直接進(jìn)行 Join 計(jì)算而不需要考慮數(shù)據(jù)的 Shuffle 問題了。
數(shù)據(jù)已經(jīng)預(yù)先分區(qū),直接在本地進(jìn)行 Join 計(jì)算
四種 Shuffle 方式對(duì)比
Shuffle方式 | 網(wǎng)絡(luò)開銷 | 物理算子 | 適用場景 |
---|---|---|---|
BroadCast | N * T(R) | Hash Join / Nest Loop Join | 通用 |
Shuffle | T(S) + T(R) | Hash Join | 通用 |
Bucket Shuffle | T(R) | Hash Join | Join條件中存在左表的分布式列,且左表執(zhí)行時(shí)為單分區(qū) |
Colocate | 0 | Hash Join | Join條件中存在左表的分布式列,切左右表同屬于一個(gè)Colocate Group |
N : 參與 Join 計(jì)算的 Instance 個(gè)數(shù)
T(關(guān)系) : 關(guān)系的 Tuple 數(shù)目
上面這 4 種方式靈活度是從高到低的,它對(duì)這個(gè)數(shù)據(jù)分布的要求是越來越嚴(yán)格,但 Join 計(jì)算的性能也是越來越好的。
Runtime Filter Join 優(yōu)化
Doris 在進(jìn)行 Hash Join 計(jì)算時(shí)會(huì)在右表構(gòu)建一個(gè)哈希表,左表流式的通過右表的哈希表從而得出 Join 結(jié)果。而 RuntimeFilter 就是充分利用了右表的 Hash 表,在右表生成哈希表的時(shí),同時(shí)生成一個(gè)基于哈希表數(shù)據(jù)的一個(gè)過濾條件,然后下推到左表的數(shù)據(jù)掃描節(jié)點(diǎn)。通過這樣的方式,Doris 可以在運(yùn)行時(shí)進(jìn)行數(shù)據(jù)過濾。
假如左表是一張大表,右表是一張小表,那么利用右表生成的過濾條件就可以把絕大多數(shù)在 Join 層要過濾的數(shù)據(jù)在數(shù)據(jù)讀取時(shí)就提前過濾,這樣就能大幅度的提升 Join 查詢的性能。
當(dāng)前 Doris 支持三種類型 RuntimeFilter
- 一種是 IN,很好理解,將一個(gè) hashset 下推到數(shù)據(jù)掃描節(jié)點(diǎn)。
- 第二種就是 BloomFilter,就是利用哈希表的數(shù)據(jù)構(gòu)造一個(gè) BloomFilter,然后把這個(gè) BloomFilter 下推到查詢數(shù)據(jù)的掃描節(jié)點(diǎn)。。
- 最后一種就是 MinMax,就是個(gè) Range 范圍,通過右表數(shù)據(jù)確定 Range 范圍之后,下推給數(shù)據(jù)掃描節(jié)點(diǎn)。
Runtime Filter 適用的場景有兩個(gè)要求:
- 第一個(gè)要求就是左表大右表小,因?yàn)闃?gòu)建 Runtime Filter是需要承擔(dān)計(jì)算成本的,包括一些內(nèi)存的開銷。
- 第二個(gè)要求就是左右表 Join 出來的結(jié)果很少,說明這個(gè) Join 可以過濾掉左表的絕大部分?jǐn)?shù)據(jù)。
當(dāng)符合上面兩個(gè)條件的情況下,開啟 Runtime Filter 就能收獲比較好的效果
當(dāng) Join 列為左表的 Key 列時(shí),RuntimeFilter 會(huì)下推到存儲(chǔ)引擎。Doris 本身支持延遲物化,
延遲物化簡單來說是這樣的:假如需要掃描 A、B、C 三列,在 A 列上有一個(gè)過濾條件: A 等于 2,要掃描 100 行的話,可以先把 A 列的 100 行掃描出來,再通過 A = 2 這個(gè)過濾條件過濾。之后通過過濾完成后的結(jié)果,再去讀取 B、C 列,這樣就能極大的降低數(shù)據(jù)的讀取 IO。所以說 Runtime Filter 如果在 Key 列上生成,同時(shí)利用 Doris 本身的延遲物化來進(jìn)一步提升查詢的性能。
Runtime Filter 類型
Doris 提供了三種不同的 Runtime Filter 類型:
- IN 的優(yōu)點(diǎn)就是效果過濾效果明顯,且快速。它的缺點(diǎn)首先第一個(gè)它只適用于 BroadCast,第二,它右表超過一定數(shù)據(jù)量的時(shí)候就失效了,當(dāng)前 Doris 目前配置的是1024,即右表如果大于 1024,IN 的 Runtime Filter 就直接失效了。
- MinMax 的優(yōu)點(diǎn)是開銷比較小。它的缺點(diǎn)就是對(duì)數(shù)值列還有比較好的效果,但對(duì)于非數(shù)值列,基本上就沒什么效果。
- Bloom Filter 的特點(diǎn)就是通用,適用于各種類型、效果也比較好。缺點(diǎn)就是它的配置比較復(fù)雜并且計(jì)算較高。
Join Reorder
數(shù)據(jù)庫一旦涉及到多表 Join,Join 的順序?qū)φ麄€(gè) Join 查詢的性能是影響很大的。假設(shè)有三張表 Join,參考下面這張圖,左邊是 a 表跟 b 張表先做 Join,中間結(jié)果的有 2000 行,然后與 c 表再進(jìn)行 Join 計(jì)算。
接下來看右圖,把 Join 的順序調(diào)整了一下。把 a 表先與 c 表 Join,生成的中間結(jié)果只有 100,然后最終再與 b 表 Join 計(jì)算。最終的 Join 結(jié)果是一樣的,但是它生成的中間結(jié)果有 20 倍的差距,這就會(huì)產(chǎn)生一個(gè)很大的性能 Diff 了。
Doris 目前支持基于規(guī)則的 Join Reorder 算法。它的邏輯是:
- 讓大表、跟小表盡量做 Join,它生成的中間結(jié)果是盡可能小的。
- 把有條件的 Join 表往前放,也就是說盡量讓有條件的 Join 表進(jìn)行過濾
- Hash Join 的優(yōu)先級(jí)高于 Nest Loop Join,因?yàn)?Hash join 本身是比 Nest Loop Join 快很多的。
Doris Join 調(diào)優(yōu)方法
Doris Join 調(diào)優(yōu)的方法:
- 利用 Doris 本身提供的 Profile,去定位查詢的瓶頸。Profile 會(huì)記錄 Doris 整個(gè)查詢當(dāng)中各種信息,這是進(jìn)行性能調(diào)優(yōu)的一手資料。。
- 了解 Doris 的 Join 機(jī)制,這也是第二部分跟大家分享的內(nèi)容。知其然知其所以然、了解它的機(jī)制,才能分析它為什么比較慢。
- 利用 Session 變量去改變 Join 的一些行為,從而實(shí)現(xiàn) Join 的調(diào)優(yōu)。
- 查看 Query Plan 去分析這個(gè)調(diào)優(yōu)是否生效。
上面的 4 步基本上完成了一個(gè)標(biāo)準(zhǔn)的 Join 調(diào)優(yōu)流程,接著就是實(shí)際去查詢驗(yàn)證它,看看效果到底怎么樣。
如果前面 4 種方式串聯(lián)起來之后,還是不奏效。這時(shí)候可能就需要去做 Join 語句的改寫,或者是數(shù)據(jù)分布的調(diào)整、需要重新去 Recheck 整個(gè)數(shù)據(jù)分布是否合理,包括查詢 Join 語句,可能需要做一些手動(dòng)的調(diào)整。當(dāng)然這種方式是心智成本是比較高的,也就是說要在嘗試前面方式不奏效的情況下,才需要去做進(jìn)一步的分析。
調(diào)優(yōu)案例實(shí)戰(zhàn)
案例一
一個(gè)四張表 Join 的查詢,通過 Profile 的時(shí)候發(fā)現(xiàn)第二個(gè) Join 耗時(shí)很高,耗時(shí) 14 秒。
進(jìn)一步分析 Profile 之后,發(fā)現(xiàn) BuildRows,就是右表的數(shù)據(jù)量是大概 2500 萬。而 ProbeRows ( ProbeRows 是左表的數(shù)據(jù)量)只有 1 萬多。這種場景下右表是遠(yuǎn)遠(yuǎn)大于左表,這顯然是個(gè)不合理的情況。這顯然說明 Join 的順序出現(xiàn)了一些問題。這時(shí)候嘗試改變 Session 變量,開啟 Join Reorder。
set enable_cost_based_join_reorder = true
這次耗時(shí)從 14 秒降到了 4 秒,性能提升了 3 倍多。
此時(shí)再 Check Profile 的時(shí)候,左右表的順序已經(jīng)調(diào)整正確,即右表是大表,左表是小表?;谛”砣?gòu)建哈希表,開銷是很小的,這就是典型的一個(gè)利用 Join Reorder 去提升 Join 性能的一個(gè)場景
案例二
存在一個(gè)慢查詢,查看 Profile 之后,整個(gè) Join 節(jié)點(diǎn)耗時(shí)大概44秒。它的右表有 1000 萬,左表有 6000 萬,最終返回的結(jié)果也只有 6000 萬。
這里可以大致的估算出過濾率是很高的,那為什么 Runtime Filter 沒有生效呢?通過 Query Plan 去查看它,發(fā)現(xiàn)它只開啟了 IN 的 Runtime Filter。
當(dāng)右表超過1024行的話, IN 是不生效的,所以根本起不到什么過濾的效果,所以嘗試調(diào)整 RuntimeFilter 的類型。
這里改為了 BloomFilter,左表的 6000 萬條數(shù)據(jù)過濾了 5900 萬條。基本上 99% 的數(shù)據(jù)都被過濾掉了,這個(gè)效果是很顯著的。查詢也從原來的 44 秒降到了 13 秒,性能提升了大概也是三倍多。
案例三
下面是一個(gè)比較極端的 Case,通過一些環(huán)境變量調(diào)優(yōu)也沒有辦法解決,因?yàn)樗婕暗?SQL Rewrite,所以這里列出來了原始的 SQL 。
select 100.00 * sum (case when P_type like 'PROMOS' then 1 extendedprice * (1 - 1 discount) else 0 end ) / sum(1 extendedprice * (1 - 1 discount)) as promo revenue from lineitem, part where 1_partkey = p_partkey and 1_shipdate >= date '1997-06-01' and 1 shipdate < date '1997-06-01' + interval '1' month
這個(gè) Join 查詢是很簡單的,單純的一個(gè)左右表的 Join 。當(dāng)然它上面有一些過濾條件,打開 Profile 的時(shí)候,發(fā)現(xiàn)整個(gè)查詢 Hash Join 執(zhí)行了三分多鐘,它是一個(gè) BroadCast 的 Join,它的右表有 2 億條,左表只有 70 萬。在這種情況下選擇了 Broadcast Join 是不合理的,這相當(dāng)于要把 2 億條做一個(gè) Hash Table,然后用 70 萬條遍歷兩億條的 Hash Table ,這顯然是不合理的。
為什么會(huì)產(chǎn)生不合理的 Join 順序呢?其實(shí)這個(gè)左表是一個(gè) 10 億條級(jí)別的大表,它上面加了兩個(gè)過濾條件,加完這兩個(gè)過濾條件之后, 10 億條的數(shù)據(jù)就剩 70 萬條了。但 Doris 目前沒有一個(gè)好的統(tǒng)計(jì)信息收集的框架,所以它不知道這個(gè)過濾條件的過濾率到底怎么樣。所以這個(gè) Join 順序安排的時(shí)候,就選擇了錯(cuò)誤的 Join 的左右表順序,導(dǎo)致它的性能是極其低下的。
下圖是改寫完成之后的一個(gè) SQL 語句,在 Join 后面添加了一個(gè)Join Hint,在Join 后面加一個(gè)方括號(hào),然后把需要的 Join 方式寫入。這里選擇了 Shuffle Join,可以看到右邊它實(shí)際查詢計(jì)劃里面看到這個(gè)數(shù)據(jù)確實(shí)是做了 Partition ,原先 3 分鐘的耗時(shí)通過這樣的改寫完之后只剩下 7 秒,性能提升明顯
Doris Join 調(diào)優(yōu)建議
最后我們總結(jié) Doris Join 優(yōu)化調(diào)優(yōu)的四點(diǎn)建議:
- 第一點(diǎn):在做 Join 的時(shí)候,要盡量選擇同類型或者簡單類型的列,同類型的話就減少它的數(shù)據(jù) Cast,簡單類型本身 Join 計(jì)算就很快。
- 第二點(diǎn):盡量選擇 Key 列進(jìn)行 Join, 原因前面在 Runtime Filter 的時(shí)候也介紹了,Key 列在延遲物化上能起到一個(gè)比較好的效果。
- 第三點(diǎn):大表之間的 Join ,盡量讓它 Co-location ,因?yàn)榇蟊碇g的網(wǎng)絡(luò)開銷是很大的,如果需要去做 Shuffle 的話,代價(jià)是很高的。
- 第四點(diǎn):合理的使用 Runtime Filter,它在 Join 過濾率高的場景下效果是非常顯著的。但是它并不是萬靈藥,而是有一定副作用的,所以需要根據(jù)具體的 SQL 的粒度做開關(guān)。
- 最后:要涉及到多表 Join 的時(shí)候,需要去判斷 Join 的合理性。盡量保證左表為大表,右表為小表,然后 Hash Join 會(huì)優(yōu)于 Nest Loop Join。必要的時(shí)可以通過 SQL Rewrite,利用 Hint 去調(diào)整 Join 的順序。
以上就是Doris Join 優(yōu)化原理文檔詳解的詳細(xì)內(nèi)容,更多關(guān)于Doris Join 優(yōu)化原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
TDSQL 安裝部署附圖的實(shí)現(xiàn)(圖文)
這篇文章主要介紹了TDSQL 安裝部署附圖的實(shí)現(xiàn)(圖文),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10ACCESS轉(zhuǎn)化成SQL2000需要注意的幾個(gè)問題小結(jié)
ACCESS轉(zhuǎn)化成SQL2000需要注意的幾個(gè)問題小結(jié)...2007-06-06Hadoop 2.x偽分布式環(huán)境搭建詳細(xì)步驟
這篇文章主要為大家詳細(xì)介紹了Hadoop 2.x偽分布式環(huán)境搭建詳細(xì)步驟,感興趣的朋友可以參考一下2016-05-05關(guān)于面試中常問的數(shù)據(jù)庫回表問題
這篇文章主要介紹了關(guān)于面試中常問的數(shù)據(jù)庫回表問題,回表就是先通過數(shù)據(jù)庫索引掃描出數(shù)據(jù)所在的行,再通過行主鍵id取出索引中未提供的數(shù)據(jù),即基于非主鍵索引的查詢需要多掃描一棵索引樹,需要的朋友可以參考下2023-07-07數(shù)據(jù)庫的三級(jí)模式和兩級(jí)映射介紹
在這里大家一定要注意三級(jí)模式中的概念模式對(duì)應(yīng)的是數(shù)據(jù)庫設(shè)計(jì)的邏輯模型,而不是概念模型(E-R模型),一定不要弄混了2012-10-10在數(shù)據(jù)庫中如何高效的實(shí)現(xiàn)訂座功能
這篇文章主要給大家介紹了關(guān)于在數(shù)據(jù)庫中如何高效的實(shí)現(xiàn)訂座功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11建立在Tablestore的Wifi設(shè)備監(jiān)管系統(tǒng)架構(gòu)實(shí)現(xiàn)
一般大公司會(huì)有許多園區(qū),園區(qū)內(nèi)會(huì)有不同部門的同事在一起辦公。每個(gè)園區(qū)內(nèi)都要配備大量的Wifi設(shè)備從而為園區(qū)同事提供方便的上網(wǎng)服務(wù)。因此,集團(tuán)需要一套完善的監(jiān)管系統(tǒng)維護(hù)所有的Wifi設(shè)備。需要的朋友來一起學(xué)習(xí)下吧2019-05-05Sql Server 和 Access 操作數(shù)據(jù)庫結(jié)構(gòu)Sql語句小結(jié)
Sql Server 和 Access 操作數(shù)據(jù)庫結(jié)構(gòu)Sql語句小結(jié)...2007-06-06隱式轉(zhuǎn)換引起的sql慢查詢實(shí)戰(zhàn)記錄
大家知道數(shù)據(jù)庫為了提高查詢速度,增加索引是必不可少的,但是有些時(shí)候即使你加了索引也不定有效果,這篇文章主要給大家介紹了一次因?yàn)殡[式轉(zhuǎn)換引起的sql慢查詢的相關(guān)資料,需要的朋友可以參考下。2018-04-04