hive 两个没有null指定的表左关联的结果有null_Hive的优化原则
原文鏈接:https://zhuanlan.zhihu.com/p/174469951
本篇將 Hive 的優(yōu)化分成三個部分:
- 第一部分是 SQL 通用語法優(yōu)化,
- 第二部分是針對 Hive 所具有的數(shù)據(jù)傾斜的優(yōu)化,
- 第三部分則介紹一些通用性的 Hive 參數(shù)設置優(yōu)化。
一、語法優(yōu)化
SQL 的語法優(yōu)化本質上是如何用更少的計算資源干相同的活,基于此延伸出幾條原則,這幾條原則又拓展出對應的一些具體方法:
原則1:取更少的數(shù)
這條原則特別樸素,只要數(shù)據(jù)量少了運算的效率自然會提升,但如何能夠取更少數(shù)的同時不影響結果呢?
1、不要用 select *
這條不多說了,一些寬表少則二三十,多則上百列,而實際絕大多數(shù)都是我們不需要的,在 select 時只挑選后續(xù)需要用到字段而不是 select *,那么運算時間會成倍減少。
2、謂詞下推
謂詞下推指:
將過濾表達式盡可能移動至靠近數(shù)據(jù)源的位置,以使真正執(zhí)行時能直接跳過無關的數(shù)據(jù)。
簡單來說就是把 where 語句盡可能挪到最底層的位置,在最底層就把不需要的數(shù)據(jù)都過濾掉,然后讓真正需要的數(shù)據(jù)參與運算,而不是留到最后才進行過濾。
根據(jù)謂詞下推的思想,可對下面的代碼進行優(yōu)化:
select t1.name, t2.age, t2.class, t2.income from t1 join t2 on t1.id = t2.id where t2.age > 18優(yōu)化后:
select t1.name, t2.age, t2.class, t2.income from t1join
(select id, age, class, income from t2 where id > 18) t2
on t1.id = t2.id
3、多用子查詢
基于謂詞下推的思想,我們還可以做進一步的優(yōu)化,即多用子查詢,上面的代碼可進一步優(yōu)化成如下樣子:
select t1.name, t2.age, t2.class, t2.income from(select id, name from t1) t1
join
(select id, age, class, income from t2 where age > 18) t2
on t1.id = t2.id
采用子查詢后,盡管會增加 job 數(shù)但提前把數(shù)據(jù)完成了過濾,還提高了代碼的可讀性,尤其是當需要關聯(lián)的表和條件成倍增加后,可讀性將會非常重要。
4、子查詢的去重
當子查詢的表中所需的字段存在重復值,那么對這些字段提前進行去重再進行關聯(lián)同樣會提高運算效率。還是以上面的代碼為例:
select t1.name, t2.age, t2.class, t2.income from(select id, name from t1) t1
join
(select id, age, class, income from t2 where age > 18 group by id, age, class, income) t2
on t1.id = t2.id
至于為什么用 group by 而不是 distinct 去重會在數(shù)據(jù)傾斜部分進行解釋。
5、過濾null值
當關聯(lián)所用到的字段包含了太多 null 時,需要從業(yè)務的角度考慮這些為 null 的數(shù)據(jù)是否有存在的必要,如果不必要的話盡早過濾掉,避免影響關聯(lián)的效率。
如果確實需要用到,則可用 rand() 把數(shù)據(jù)均勻分布在不同的 reduce 上,避免數(shù)據(jù)傾斜,詳細可見第二部分,此處僅列出代碼:
selectn.*,
o.name
from
nullidtable n
full join
bigtable o
on nvl(n.id,rand())=o.id
原則2:不排序
SQL 中進行排序是要消耗計算資源的,在 Hive 中這種資源消耗會更加明顯。子查詢里不要排序這一點就不多說了,子查詢中排序是毫無意義的,同時在最后的結果步也盡可能少排序,排序這需求完全可以通過交互查詢的UI或把結果數(shù)據(jù)導出進行替代解決。當然,如果進行查詢時沒有UI系統(tǒng),或者不方便把數(shù)據(jù)導出,或者你就是想即時看到數(shù)據(jù)的排序情況,就當這條建議不存在就好,但在子查詢里排序仍然還是毫無意義的。
原則3:分步
該原則主要目的是把大數(shù)據(jù)集拆成小數(shù)據(jù)集來運行。
1、減少在 selec 時用 case when
有時出結果時需要用 case when 進行分類輸出,如下面例子
selectdt,
count(distinct case when id=1 then user_id else null end) as id_1_cnt,
count(distinct case when id=2 then user_id else null end) as id_2_cnt
from table_1
where dt between '20200511' and '20200915'
group by dt
但是實測發(fā)現(xiàn) case when 的執(zhí)行效率很低,當數(shù)據(jù)量太大的時候甚至會跑不出數(shù),因此上面的代碼可優(yōu)化成如下形式:
selectt1.dt,
t1.id_1_cnt,
t2.id_2_cnt
from
(select
dt,
count(distinct user_id) as id_1_cnt
from table_1
where dt between '20200511' and '20200915' and id=1
group by dt) t1
left join
(select
dt,
count(distinct user_id) as id_2_cnt
from table_1
where dt between '20200511' and '20200915' and id=2
group by dt) t2
on t1.dt=t2.dt
當數(shù)據(jù)量很大或者 select 時有太多的 case when,采用上面的方式其執(zhí)行效率會提高 10 倍以上。
2、多用臨時表
當需要建的表其邏輯非常復雜時,需要考慮用臨時表的方式把中間邏輯分布執(zhí)行,一來方便閱讀、修改和維護,二來減少硬盤的開銷(相較于建中間表的方式)。
3、where+union all
當需要根據(jù)某字段分類匯總時發(fā)現(xiàn)運行速度很慢甚至跑不出結果,那么有可能是因為某一類型的數(shù)據(jù)樣本量過大造成數(shù)據(jù)傾斜,此時可考慮通過 where 過濾 + union all 合并的方法分步統(tǒng)計和匯總來處理該問題。
優(yōu)化前:
selectage,
count(distinct id) as id_cnt
from age_table
group by age
優(yōu)化后:
selectage,
count(distinct id) as id_cnt
from age_table
where age<35
group by age
union all
select
age,
count(distinct id) as id_cnt
from age_table
where age>=35
group by age
SQL 語句的優(yōu)化方法貴精不貴多,牢記上述原則和方法在日常取數(shù)建表寫 SQL 時大部分情況下就已經(jīng)接近最優(yōu)效率了。
二、數(shù)據(jù)傾斜
在展開數(shù)據(jù)傾斜的優(yōu)化之前,需要先了解 Hive 所采用 MapReduce 的原理
以上圖為例,快速過一遍 MapReduce 的工作流程:
1、首先把需要處理的數(shù)據(jù)文件上傳到 HDFS 上,然后這些數(shù)據(jù)會被分為好多個小的分片,然后每個分片對應一個 map 任務,推薦情況下分片的大小等于 block 塊的大小。然后 map 的計算結果會暫存到一個內存緩沖區(qū)內,該緩沖區(qū)默認為 100M,等緩存的數(shù)據(jù)達到一個閾值的時候,默認情況下是 80%,然后會在磁盤創(chuàng)建一個文件,開始向文件里邊寫入數(shù)據(jù)。
2、map 任務的輸入數(shù)據(jù)的格式是 key-value 對的形式,然后 map 在往內存緩沖區(qū)里寫入數(shù)據(jù)的時候會根據(jù) key 進行排序,同樣溢寫到磁盤的文件里的數(shù)據(jù)也是排好序的,最后 map 任務結束的時候可能會產(chǎn)生多個數(shù)據(jù)文件,然后把這些數(shù)據(jù)文件再根據(jù)歸并排序合并成一個大的文件。
3、然后每個分片都會經(jīng)過 map 任務后產(chǎn)生一個排好序的文件,同樣文件的格式也是 key-value 對的形式,然后通過對 key 進行 hash 的方式把數(shù)據(jù)分配到不同的 reduce 里邊去,這樣對每個分片的數(shù)據(jù)進行 hash,再把每個分片分配過來的數(shù)據(jù)進行合并,合并過程中也是不斷進行排序的。最后數(shù)據(jù)經(jīng)過 reduce 任務的處理就產(chǎn)生了最后的輸出。
簡單來說,map 階段負責不同節(jié)點上一部分數(shù)據(jù)的統(tǒng)計工作,reduce 階段負責匯總聚合的工作。
有時一個 reduce 可以處理多個任務,但一些全局的工作只能讓一個 reduce 負責,例如統(tǒng)計總行數(shù)、distinct去重等,此時就 reduce 就不能有多個實例并發(fā)執(zhí)行,這就會造成其他 reduce 的任務已經(jīng)執(zhí)行完了,而負責全局的 reduce 還沒執(zhí)行完,這就是數(shù)據(jù)傾斜的本質,因此避免數(shù)據(jù)傾斜的核心在于均勻分配任務。
1、數(shù)據(jù)量大的時候用group by
當需要對數(shù)據(jù)進行去重時,在數(shù)據(jù)量較大的情況下可以選擇用 group by 而不是 distinct,原理如下:
默認情況下,map 階段同一 key 數(shù)據(jù)分發(fā)給一個 reduce,當一個 key 數(shù)據(jù)過大時就會發(fā)生數(shù)據(jù)傾斜了。但是并不是所有的聚合操作都只能在 reduce 完成,很多聚合操作也可以先在 map 進行部分聚合,最后在 reduce 端得出最終結果。
開啟 Map 端聚合參數(shù)設置
(1)是否在 Map 端進行聚合,默認為 true
set hive.map.aggr = true;
(2)在 Map 端進行聚合操作的條目數(shù)目
set hive.groupby.mapaggr.checkinterval = 100000;
(3)有數(shù)據(jù)傾斜的時候進行負載均衡(默認是false)
set hive.groupby.skewindata = true;
當選項設定為 true,生成的查詢計劃會有兩個MR Job。第一個MR Job中,Map 的輸出結果會隨機分布到 Reduce 中,每個 Reduce 做部分聚合操作,并輸出結果,這樣處理的結果是相同的 Group By Key 有可能被分發(fā)到不同的 Reduce 中,從而達到負載均衡的目的;第二個MR Job再根據(jù)預處理的數(shù)據(jù)結果按照 Group By Key 分布到 Reduce 中(這個過程可以保證相同的Group By Key被分布到同一個Reduce中),最后完成最終的聚合操作。
而與之相對應的,distinct 則只會用一個 reduce 來執(zhí)行,造成數(shù)據(jù)量過大而讓整體任務執(zhí)行時間過長或無法完成。
但是需要注意的是,用 group by 來去重會額外增加一個子查詢,只有當數(shù)據(jù)量很大的情況或任務執(zhí)行中出現(xiàn)嚴重的數(shù)據(jù)傾斜,group by 去重后 count 才會比 count(distinct) 效率更高。
2、Mapjoin
如果不指定 MapJoin 或者不符合 MapJoin 的條件,那么 Hive 解析器會將 Join 操作轉換成 Common Join,即:在 Reduce 階段完成 join。容易發(fā)生數(shù)據(jù)傾斜。可以用 MapJoin 把小表全部加載到內存在 map 端進行 join,避免 reducer 處理。
(1)設置自動選擇 Mapjoin
set hive.auto.convert.join = true; 默認為true
(2)大表小表的閾值設置(默認25M以下認為是小表):
set hive.mapjoin.smalltable.filesize=25000000;
數(shù)據(jù)傾斜的處理在 Hive 優(yōu)化中是一個大課題,實際場景中所遇到的 Hive 任務執(zhí)行過長或報錯有80%都與數(shù)據(jù)傾斜有關,后續(xù)有機會的話可能專門寫一篇針對解決數(shù)據(jù)傾斜的文章。
三、參數(shù)優(yōu)化
該部分羅列了一些常用的 Hive 參數(shù)設置,并逐條做簡單介紹
1、set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveFormat;(默認開啟)
將多個小文件打包作為一個整體的 inputsplit,減少 map 任務數(shù)
2、set hive.merge.mapfiles=true;(默認值為真)
合并 map 端小文件的輸出
3、set hive.auto.convert.join=true;
開啟 mapjoin
4、set hive.mapjoin.smalltable.filesize=25000000; (默認25M)
設置 mapjoin 的開啟閾值
5、set hive.optimize.skewjoin=true;
有數(shù)據(jù)傾斜的時候進行負載均衡
6、set hive.skewjoin.key=100000;
表示當記錄條數(shù)超過100000時采用 skewjoin 操作
7、set hive.exec.parallel=true;
多個 join 多個 union all 優(yōu)化,開啟不同 stage 任務并行計算
8、set hive.exec.parallel.thread.number=16;(默認為8)
同一個 SQL 允許最大并行度
9、set hive.map.aggr=true;
group by 數(shù)據(jù)傾斜優(yōu)化 設置在 map 端進行聚合
10、set hive.groupby.skewindata=true;
group by 數(shù)據(jù)傾斜優(yōu)化
11、set hive.exec.mode.local.auto=true;
開啟本地模式
12、set mapred.compress.map.output=true;
開啟中間壓縮
以上是 Hive 通用屬性的設置,下面的參數(shù)主要目的是控制 map 和 reduce 的數(shù)量,需要依情況而設定:
13、set hive.merge.smallfiles.avgsize=16000000;
平均文件大小,是決定是否執(zhí)行合并操作的閾值
14、set mapred.min.split.size.per.node=128000000;
低于 128M 就算小文件,數(shù)據(jù)在一個節(jié)點會合并,在多個不同的節(jié)點會把數(shù)據(jù)抓取過來進行合并
15、set mapred.min.split.size.per.rack=64000000;
每個機架處理的最小 split
16、set mapred.max.split.size=256000000;
決定每個 map 處理的最大的文件大小
17、set mapred.min.split.size=10000000;
決定每個 map 處理的最小的文件大小
18、set hive.merge.size.per.task=256000000;(默認值為256000000)
對 map 個數(shù)進行設置
19、set mapred.reduce.tasks=10;
設置 reduce 的數(shù)量
20、set hive.exec.reducers.bytes.per.reducer=536870912;(512M)
調整每個 reduce 處理數(shù)據(jù)量的大小
以上關于 map 和 reduce 的參數(shù)需要根據(jù)實際情況設置,具體的設置邏輯礙于篇幅所限就不展開了,如果有機會的話需要單獨列一篇作詳細介紹。
除了以上的優(yōu)化方向外,還可以通過設置 Hive 的文件格式來提高效率,目前優(yōu)化做得最好的文件格式就是 ORCfile,可以在建表時通過 stored as ORC 來調用。另外,可以根據(jù)實際工作需要把一些常用的統(tǒng)計匯總邏輯用中間表的形式存儲起來,便于后續(xù)查詢。
Hive 的優(yōu)化是一個系統(tǒng)性的工作,本篇僅列一二,但是由于以后是 Spark 以及其他更優(yōu)秀引擎的天下了,所以如果以后還要對 Hive 進行優(yōu)化,那大概就是換一個語言吧(不是)。
如果覺得還有幫助的話,你的關注和轉發(fā)是對我最大的支持,O(∩_∩)O:
總結
以上是生活随笔為你收集整理的hive 两个没有null指定的表左关联的结果有null_Hive的优化原则的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 子窗体 记录选择_如何设计一个简单的Ac
- 下一篇: java 虚拟机 字节码,JAVA虚拟机