HiveSql優化

Hive SQL的各種優化方法基本 都和數據傾斜密切相關。
  Hive的優化分爲join相關的優化和join無關的優化,從項目的實際來說,join相關的優化佔了Hive優化的大部分內容,而join相關的優化又分爲mapjoin可以解決的join優化和mapjoin無法解決的join優化。

1、Hive優化(這是重點)

在實際的Hive SQL開發的過程中,Hive SQL 性能的問題上實際上只有一小部分和數據傾斜有關,很多時候,Hive SQL運行慢是由於開發人員對於使用的數據瞭解不夠以及一些不良的習慣引起的。

開發人員需要確定以下幾點:

1、指標是否一定要通過明細表得到,可否通過其他量級比較小的表得到,重複代碼是否可以使用中間表來替代

2、開發過程中是否真的需要掃描那麼多分區,舉個例子你要做一張月維度表的報表,月維度數據很大,如果在測試demo中使用where月作爲限制條件是不是要花很多時間,僅僅從調通代碼測試樣本數據的角度出發,使用where日條件量級比較小的限制條件會不會更好呢?(這個是重點,where條件的使用)

3、儘量不要使用select * from your_table這樣的方式,用到哪些列就指定哪些列,另外WHERE條件中儘量添加過濾條件,以去掉無關的行,從而減少整個MapReduce任務需要處理、分發的數據量。

4、輸入文件不要是大量的小文件,Hive默認的Input Split是128MB(可配置),小文件可先合併成大文件。

    我們知道文件數目小,容易在文件存儲端造成瓶頸,給 HDFS 帶來壓力,影響處理效率。對此,可以通過合併Map和Reduce的結果文件來消除這樣的影響。

    用於設置合併屬性的參數有:

     調優參數:

是否合併Map輸出文件:hive.merge.mapfiles=true(默認值爲真)
是否合併Reduce 端輸出文件:hive.merge.mapredfiles=false(默認值爲假)
合併文件的大小:hive.merge.size.per.task=25610001000(默認值爲 256000000)

1.1、join無關的優化

Hive SQL性能問題基本上大部分都是和JOIN相關,對於和join無關的問題主要有group by相關的傾斜和count distinct相關的優化

1.11、group by引起的傾斜優化(若無法避免則設置調優參數進行hive map端的負載均衡)
  group by引起的傾斜主要是輸入數據行按照group by列分別布均勻引起的,比如,假設按照供應商對銷售明細事實表來統計訂單數,那麼部分大供應商的訂單量顯然非常大,而多數供應商的訂單量就一般,

由於group by 的時候是按照供應商的ID分發到每個Reduce Task,那麼此時分配到大供應商的Reduce task就分配了更多的訂單,從而導致數據傾斜。

對應group by引起的數據傾斜,優化措施非常簡單,只需要設置下面參數即可:

set hive.map.aggr = true

set hive.groupby.skewindata = true

此時,Hive在數據傾斜的時候回進行負載均衡。

注:

hive和其它關係數據庫一樣,支持count(distinct)操作,但是對於大數據量中,如果出現數據傾斜時,會使得性能非常差,解決辦法爲設置數據負載均衡,其設置方法爲設置hive.groupby.skewindata參數
hive (default)> set hive.groupby.skewindata;
hive.groupby.skewindata=false
默認該參數的值爲false,表示不啓用,要啓用時,可以set hive.groupby.skewindata=ture;進行啓用。
當啓用時,能夠解決數據傾斜的問題,但如果要在查詢語句中對多個字段進行去重統計時會報錯。
hive> set hive.groupby.skewindata=true;
hive> select count(distinct id),count(distinct x) from test;
FAILED: SemanticException [Error 10022]: DISTINCT on different columns not supported with skew in data
下面這種方式是可以正常查詢
hive>select count(distinct id, x) from test;

1.12、count distinct優化
  在Hive開發過程中,應該小心使用count distinct,因爲很容易引起性能問題,比如下面的SQL:

select count(distinct user) from some_table;

由於必須去重,因此Hive將會把Map階段的輸出全部分佈到一個Reduce Task上,此時很容易引起性能問題,對於這種情況,可以通過先group by再count的方式優化,優化後的SQL如下:

select count(*)

from (select user from some_table group by user) temp;

其原理爲:利用group by去重,再統計group by 的行數目。

 1.2大小表join優化



1.21默認hivesql大小表誰在前合適呢:答案遵循小表在前原則 

原因:底層mr中 Reduce 階段,位於 Join 操作符左邊的表的內容會被加載進內存,載入條目較少的表 可以有效減少 OOM(out of memory)即內存溢出。所以對於同一個 key 來說,對應的 value 值小的放前,大的放後,這便是“小表放前”原則。 若一條語句中有多個 Join,依據 Join 的條件相同與否,有不同的處理方法。具體如下:

key相同:

如果 Join 的 key 相同,不管有多少個表,都會則會合併爲一個 Map-Reduce
一個 Map-Reduce 任務,而不是 ‘n’ 個
在做 OUTER JOIN 的時候也是一樣

key不同:

如果 Join 的條件不相同,比如:

INSERT OVERWRITE TABLE pv_users

SELECT pv.pageid, u.age FROM page_view p

JOIN user u ON (pv.userid = u.userid)

JOIN newuser x on (u.age = x.age);

Map-Reduce 的任務數目和 Join 操作的數目是對應的,上述查詢和以下查詢是等價的:

INSERT OVERWRITE TABLE tmptable

SELECT * FROM page_view p JOIN user u

ON (pv.userid = u.userid);

INSERT OVERWRITE TABLE pv_users

SELECT x.pageid, x.age FROM tmptable x

JOIN newuser y ON (x.age = y.age);

1.22大表join大表()

使用map join 操作 hive默認是開啓set hive.auto.convent.join=true;
格式爲/*+ MAPJOIN(pv) / 多表可表現在爲/+ MAPJOIN(b,c,d) */
Join 操作在 Map 階段完成,不再需要Reduce,前提條件是需要的數據在 Map 的過程中可以訪問到。比如查詢:

INSERT OVERWRITE TABLE pv_users

SELECT /*+ MAPJOIN(pv) */ pv.pageid, u.age

FROM page_view pv

JOIN user u ON (pv.userid = u.userid);

可以在 Map 階段完成 Join.

進階相關的參數爲:

hive.join.emit.interval = 1000
hive.mapjoin.size.key = 10000
hive.mapjoin.cache.numrows = 10000
注:這個方法的前提是小表不能太大,一般不能超過1g,要不hive會報錯,此外還需注意的一點是hdfs上的文件一般是壓縮過的,實際可能會膨脹10倍

1.23大表join大表(假如小表大小超過1g就變成大表)

一、問題場景
      問題場景如下:
      A表爲一個彙總表,彙總的是賣家買家最近N天交易彙總信息,即對於每個賣家最近N天,其每個買家共成交了多少單,總金額是多少,假設N取90天,彙總值僅取成交單數。
      A表的字段有:buyer_id、seller_id、pay_cnt_90day。
      B表爲賣家基本信息表,其字段有seller_id、sale_level,其中sale_levels是賣家的一個分層評級信息,比如吧賣家分爲6個級別:S0、S1、S2、S3、S4和S5。
      要獲得的結果是每個買家在各個級別的賣家的成交比例信息,比如:
      某買家:S0:10%;S1:20%;S2:20%;S3:10%;S4:20%;S5:10%。
      正如mapjoin中的例子一樣,第一反應是直接join兩表並統計:
      select
         m.buyer_id,
        sum(pay_cnt_90day) as pay_cnt_90day,
        sum(case when m.sale_level = 0 then pay_cnt_90day end) as pay_cnt_90day_s0,
        sum(case when m.sale_level = 1 then pay_cnt_90day end) as pay_cnt_90day_s1,
        sum(case when m.sale_level = 2 then pay_cnt_90day end) as pay_cnt_90day_s2,
        sum(case when m.sale_level = 3 then pay_cnt_90day end) as pay_cnt_90day_s3,
        sum(case when m.sale_level = 4 then pay_cnt_90day end) as pay_cnt_90day_s4,
        sum(case when m.sale_level = 5 then pay_cnt_90day end) as pay_cnt_90day_s5
      from (
        select a.buer_id, a.seller_id, b.sale_level, a.pay_cnt_90day
        from ( select buyer_id, seller_id, pay_cnt_90day from table_A) a
        join
         (select seller_id, sale_level from table_B) b
        on a.seller_id = b.seller_id
        ) m
      group by m.buyer_id
      但是此SQL會引起數據傾斜,原因在於賣家的二八準則,某些賣家90天內會有幾百萬甚至上千萬的買家,但是大部分的賣家90天內買家的數目並不多,join table_A和table_B的時候,
    ODPS會按照seller_id進行分發,table_A的大賣家引起了數據傾斜。
      但是數據本身無法用mapjoin table_B解決,因爲賣家超過千萬條,文件大小有幾個GB,超過了1GB的限制。
   二、優化方案
優化方案1
      一個很正常的想法是,儘管B表無法直接mapjoin, 但是是否可以間接mapjoin它呢?
      實際上此思路有兩種途徑:限制行和限制列。
      限制行的思路是不需要join B全表,而只需要join其在A表中存在的,對於本問題場景,就是過濾掉90天內沒有成交的賣家。
      限制列的思路是隻取需要的字段。
      加上如上的限制後,檢查過濾後的B表是否滿足了Hive mapjoin的條件,如果能滿足,那麼添加過濾條件生成一個臨時B表,然後mapjoin該表即可。採用此思路的語句如下:
      
      select
         m.buyer_id,
        sum(pay_cnt_90day) as pay_cnt_90day,
        sum(case when m.sale_level = 0 then pay_cnt_90day end) as pay_cnt_90day_s0,
        sum(case when m.sale_level = 1 then pay_cnt_90day end) as pay_cnt_90day_s1,
        sum(case when m.sale_level = 2 then pay_cnt_90day end) as pay_cnt_90day_s2,
        sum(case when m.sale_level = 3 then pay_cnt_90day end) as pay_cnt_90day_s3,
        sum(case when m.sale_level = 4 then pay_cnt_90day end) as pay_cnt_90day_s4,
        sum(case when m.sale_level = 5 then pay_cnt_90day end) as pay_cnt_90day_s5
      from (
        select /+mapjoin(b)/
          a.buer_id, a.seller_id, b.sale_level, a.pay_cnt_90day
        from ( select buyer_id, seller_id, pay_cnt_90day from table_A) a
        join
         (
           select seller_id, sale_level from table_B b0
           join
           (select seller_id from table_A group by seller_id) a0
           on b0.seller_id = a0.selller_id
          ) b
        on a.seller_id = b.seller_id
        ) m
      group by m.buyer_id
      此方案在一些情況可以起作用,但是很多時候還是無法解決上述問題,因爲大部分賣家儘管90天內買家不多,但還是有一些的,過濾後的B表仍然很多。

優化方案2
      此種解決方案應用場景是:傾斜的值是明確的而且數量很少,比如null值引起的傾斜。其核心是將這些引起傾斜的值隨機分發到Reduce,其主要核心邏輯在於join時對這些特殊值concat隨機數,
    從而達到隨機分發的目的。此方案的核心邏輯如下:
       select a.user_id, a.order_id, b.user_id
      from table_a a join table_b b
      on (case when a.user_is is null then concat(‘hive’, rand()) else a.user_id end) = b.user_id
      Hive 已對此進行了優化,只需要設置參數skewinfo和skewjoin參數,不修改SQL代碼,例如,由於table_B的值“0” 和“1”引起了傾斜,值需要做如下設置:
      set hive.optimize.skewinfo=table_B:(selleer_id) [ ( “0”) (“1”) ) ]
      set hive.optimize.skewjoin = true;
      但是方案2因爲無法解決本問題場景的傾斜問題,因爲傾斜的賣家大量存在而且動態變化。
  
   優化方案3:倍數B表,在取模join     1、通用方案
      此方案的思路是建立一個numbers表,其值只有一列int 行,比如從1到10(具體值可根據傾斜程度確定),然後放大B表10倍,再取模join。代碼如下:
      
      select
         m.buyer_id,
        sum(pay_cnt_90day) as pay_cnt_90day,
        sum(case when m.sale_level = 0 then pay_cnt_90day end) as pay_cnt_90day_s0,
        sum(case when m.sale_level = 1 then pay_cnt_90day end) as pay_cnt_90day_s1,
        sum(case when m.sale_level = 2 then pay_cnt_90day end) as pay_cnt_90day_s2,
        sum(case when m.sale_level = 3 then pay_cnt_90day end) as pay_cnt_90day_s3,
        sum(case when m.sale_level = 4 then pay_cnt_90day end) as pay_cnt_90day_s4,
        sum(case when m.sale_level = 5 then pay_cnt_90day end) as pay_cnt_90day_s5
      from (
        select a.buer_id, a.seller_id, b.sale_level, a.pay_cnt_90day
        from ( select buyer_id, seller_id, pay_cnt_90day from table_A) a
        join
         (
          select /+mapjoin(members)/
            seller_id, sale_level ,member
          from table_B
         join members
          ) b
        on a.seller_id = b.seller_id
          and mod(a.pay_cnt_90day,10)+1 = b.number
        ) m
      group by m.buyer_id
        此思路的核心在於,既然按照seller_id分發會傾斜,那麼再人工增加一列進行分發,這樣之前傾斜的值的傾斜程度會減少到原來的1/10,可以通過配置numbers表改放大倍數來降低傾斜程度,
      但這樣做的一個弊端是B表也會膨脹N倍。
     專用方案
        通用方案的思路把B表的每條數據都放大了相同的倍數,實際上這是不需要的,只需要把大賣家放大倍數即可:需要首先知道大賣家的名單,即先建立一個臨時表動態存放每天最新的大賣家(
      比如dim_big_seller),同時此表的大賣家要膨脹預先設定的倍數(1000倍)。
        在A表和B表分別新建一個join列,其邏輯爲:如果是大賣家,那麼concat一個隨機分配正整數(0到預定義的倍數之間,本例爲0~1000);如果不是,保持不變。具體代碼如下:
      
      select
         m.buyer_id,
        sum(pay_cnt_90day) as pay_cnt_90day,
        sum(case when m.sale_level = 0 then pay_cnt_90day end) as pay_cnt_90day_s0,
        sum(case when m.sale_level = 1 then pay_cnt_90day end) as pay_cnt_90day_s1,
        sum(case when m.sale_level = 2 then pay_cnt_90day end) as pay_cnt_90day_s2,
        sum(case when m.sale_level = 3 then pay_cnt_90day end) as pay_cnt_90day_s3,
        sum(case when m.sale_level = 4 then pay_cnt_90day end) as pay_cnt_90day_s4,
        sum(case when m.sale_level = 5 then pay_cnt_90day end) as pay_cnt_90day_s5
      from (
        select a.buer_id, a.seller_id, b.sale_level, a.pay_cnt_90day
        from (
          select /+mapjoin(big)/
             buyer_id, seller_id, pay_cnt_90day,
             if(big.seller_id is not null, concat( table_A.seller_id, ‘rnd’, cast( rand() * 1000 as bigint ), table_A.seller_id) as seller_id_joinkey
             from table_A
             left outer join
             --big表seller_id有重複,請注意一定要group by 後再join,保證table_A的行數保持不變
             (select seller_id from dim_big_seller group by seller_id)big
             on table_A.seller_id = big.seller_id
        ) a
        join
         (
          select /+mapjoin(big)/
            seller_id, sale_level ,
            --big表的seller_id_joinkey生成邏輯和上面的生成邏輯一樣
            coalesce(seller_id_joinkey,table_B.seller_id) as seller_id_joinkey
          from table_B
         left out join
          --table_B表join大賣家表後大賣家行數擴大1000倍,其它賣家行數保持不變
          (select seller_id, seller_id_joinkey from dim_big_seller) big
          on table_B.seller_id= big.seller_id
          ) b
        on a.seller_id_joinkey= b.seller_id_joinkey
          and mod(a.pay_cnt_90day,10)+1 = b.number
        ) m
      group by m.buyer_id
      相比通用方案,專用方案的運行效率明細好了許多,因爲只是將B表中大賣家的行數放大了1000倍,其它賣家的行數保持不變,但同時代碼複雜了很多,而且必須首先建立大數據表。
   動態一分爲二(終極方案)
      實際上方案2和3都用了一分爲二的思想,但是都不徹底,對於mapjoin不能解決的問題,終極解決方案是動態一分爲二,即對傾斜的鍵值和不傾斜的鍵值分開處理,不傾斜的正常join即可,
    傾斜的把他們找出來做mapjoin,最後union all其結果即可。
      但是此種解決方案比較麻煩,代碼複雜而且需要一個臨時表存放傾斜的鍵值。代碼如下:
      --由於數據傾斜,先找出90天買家超過10000的賣家
      insert overwrite table temp_table_B
      select
        m.seller_id, n.sale_level
      from (
        select seller_id
        from (
          select seller_id,count(buyer_id) as byr_cnt
          from table_A
          group by seller_id
          ) a
        where a.byr_cnt >10000
        ) m
      left join
      (
       select seller_id, sale_level from table_B
      ) n
      on m.seller_id = n.seller_id;
      
      --對於90天買家超過10000的賣家直接mapjoin,對其它賣家直接正常join即可。
      
      select
         m.buyer_id,
        sum(pay_cnt_90day) as pay_cnt_90day,
        sum(case when m.sale_level = 0 then pay_cnt_90day end) as pay_cnt_90day_s0,
        sum(case when m.sale_level = 1 then pay_cnt_90day end) as pay_cnt_90day_s1,
        sum(case when m.sale_level = 2 then pay_cnt_90day end) as pay_cnt_90day_s2,
        sum(case when m.sale_level = 3 then pay_cnt_90day end) as pay_cnt_90day_s3,
        sum(case when m.sale_level = 4 then pay_cnt_90day end) as pay_cnt_90day_s4,
        sum(case when m.sale_level = 5 then pay_cnt_90day end) as pay_cnt_90day_s5
      from (
        select a.buer_id, a.seller_id, b.sale_level, a.pay_cnt_90day
        from ( select buyer_id, seller_id, pay_cnt_90day from table_A) a
        join
         (
          select seller_id, a.sale_level
           from table_A a
           left join temp_table_B b
          on a.seller_id = b.seller_id
          where b.seller_id is not null
          ) b
        on a.seller_id = b.seller_id
       union all
       
       select /+mapjoin(b)/
          a.buer_id, a.seller_id, b.sale_level, a.pay_cnt_90day
        from (
           select buyer_id, seller_id, pay_cnt_90day
          from table_A
          ) a
        join
         (
           select seller_id, sale_level from table_B
          ) b
        on a.seller_id = b.seller_id
     ) m group by m.buyer_id
     ) m
     group by m.buyer_id

總結:方案1、2以及方案3中的同用方案不能保證解決大表join大表問題,因爲它們都存在種種不同的限制和特定使用場景。而方案3的專用方案和方案4是推薦的優化方案,但是它們都需要新建一個臨時表
       來存儲每日動態變化的大賣家。相對方案4來說,方案3的專用方案不需要對代碼框架進行修改,但是B表會被放大,所以一定要是是維度表,不然統計結果會是錯誤的。方案4最通用,自由度最高,
       但是對代碼的更改也最大,甚至修改更難代碼框架,可以作爲終極方案使用。

優化總結

1.提高數據敏感度和培養一個好的習慣是至關重要的,善用where,創建必要的中間表會大大提高的你工作效率

2.優化時 hive sql底層mr是hive優化的根本

長期觀察hadoop處理數據的過程,有幾個顯著的特徵:
不怕數據多,就怕數據傾斜。
對jobs數比較多的作業運行效率相對比較低,比如即使有幾百行的表,如果多次關聯多次彙總,產生十幾個jobs,沒半小時是跑不完的。map reduce作業初始化的時間是比較長的。
對sum,count來說,不存在數據傾斜問題。
對count(distinct ),效率較低,數據量一多,準出問題,如果是多count(distinct )效率更低。
優化可以從幾個方面着手:
好的模型設計事半功倍。
解決數據傾斜問題。
減少job數。
設置合理的map reduce的task數,能有效提升性能。(比如,10w+級別的計算,用160個reduce,那是相當的浪費,1個足夠)。
自己動手寫sql解決數據傾斜問題是個不錯的選擇。set hive.groupby.skewindata=true;這是通用的算法優化,但算法優化總是漠視業務,習慣性提供通用的解決方法。 Etl開發人員更瞭解業務,更瞭解數據,所以通過業務邏輯解決傾斜的方法往往更精確,更有效。
對count(distinct)採取漠視的方法,尤其數據大的時候很容易產生傾斜問題,不抱僥倖心理。自己動手,豐衣足食。
對小文件進行合併,是行至有效的提高調度效率的方法,假如我們的作業設置合理的文件數,對雲梯的整體調度效率也會產生積極的影響。
優化時把握整體,單個作業最優不如整體最優。

優化的常用手段

主要由三個屬性來決定:
hive.exec.reducers.bytes.per.reducer #這個參數控制一個job會有多少個reducer來處理,依據的是輸入文件的總大小。默認1GB。
hive.exec.reducers.max #這個參數控制最大的reducer的數量, 如果 input / bytes per reduce > max 則會啓動這個參數所指定的reduce個數。 這個並不會影響mapre.reduce.tasks參數的設置。默認的max是999。
mapred.reduce.tasks #這個參數如果指定了,hive就不會用它的estimation函數來自動計算reduce的個數,而是用這個參數來啓動reducer。默認是-1。

參數設置的影響
如果reduce太少:如果數據量很大,會導致這個reduce異常的慢,從而導致這個任務不能結束,也有可能會OOM 2、如果reduce太多: 產生的小文件太多,合併起來代價太高,namenode的內存佔用也會增大。如果我們不指定mapred.reduce.tasks, hive會自動計算需要多少個reducer。

參考資料
①《離線和實時大數據開發實戰》 | 朱松嶺
② :https://mp.weixin.qq.com/s? __biz=MzA3MDY0NTMxOQ==&mid=2247485140&idx=1&sn=dd8d05309b8e2e86b3bde6728c6932ec&chksm=9f38e5fca84f6ceae8eb4791337ccfe81fc6764890100bb2cb7f7aec2ad23b1a78e1e25f56c4&mpshare=1&scene=23&srcid=07317XgGFtD10j3e3t28uP6U#rd

③https://blog.csdn.net/tp15868352616/article/details/81295450
④https://www.iteye.com/blog/daizj-2283332

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章