HiveSQL常用(下篇:使用技巧與優化)


很高興遇到你~

 

HiveSQL使用技巧與優化

SQL執行順序:FROM->JOIN->WHERE->GROUP BY->HAVING->SELECT->ORDER BY->LIMIT 

  • distinct去重與count
--distinct去重時,如果存在NULL,結果會異常,Hive不會將null值歸為一個值處理,此時需要給NULL進行轉換
select distinct nvl(column1,''),nvl(column2,0) from t;

--count(*)、count(1)對所有行進行統計,包括null行,count(column_name)只對該列中非null的進行統計

--Hive中要避免使用count(distinct),它無法進行聚合操作,只在一個reduce上完成,容易出現性能瓶頸甚至oom內存溢出,使用group by來替代
--count distinct
select col1,count(distinct id) as did
from t
group by col1;
--使用group by優化替代
select col1,count(id) as did
from(select col1,id from t group by col1,id) as temp
group by col1;

 

  • subquerys子查詢&exists/in&left semi join
--subquerys子查詢:hive只支持from和where后的子查詢
--如果子查詢中包含null值,不能使用not in(not in會報錯,in不會)
--不推薦使用in/not in,可使用exists/not exists替代,支持子查詢中的多值匹配
--not exists和left join可以有等價寫法 --not exists select a,b from t1 where not exists(select 1 from t2 where t1.a=t2.a and t1.b=t2.b); --等價not exists的left join寫法 select t1.a,t2.b from t1 left join t2 on (t1.a=t2.a and t1.b=t2.b) where t2.a is null; --left semi join 替代 in和exists,效率更高 --LEFT SEMI JOIN(左半連接)是IN/EXISTS子句查詢的一種更高效的實現 --LEFT SEMI JOIN 的限制是:JOIN 子句中右邊的表只能在ON 子句中設置過濾條件,在WHERE 子句、SELECT 子句或其他地方過濾都不行 --LEFT SEMI JOIN 只會顯示出左邊表的字段,left semi join會掉右表中重復的記錄,不會因為右表重復key join出多條 --in/exists SELECT a.key, a.value FROM a WHERE a.key in (SELECT b.key FROM B);
--left semi join替代in/exists SELECT a.key, a.val FROM a LEFT SEMI JOIN b on (a.key = b.key)

in和exists的選取:使用exists時,內表會對外表進行循環查詢匹配,它不在乎后面內表子查詢的返回值,只在乎有沒有返回值,存在返回值,則條件為true,該條數據匹配成功,加入查詢結果集中,如果沒有返回值,條件為假,丟棄該條數據。in用法與exists一致,查詢結果和解析計划一致,in在HQL中只會查詢一次,然后把結果集存在臨時文件中,然后再與外層查詢SQL進行匹配。如果子查詢得出的結果集記錄較少,主查詢中的表較大時使用in,反之外層的主查詢記錄數較少,子查詢中的表大,優先使用exists。其實區分in和exists主要是造成了驅動順序的改變,如果是exists,那么以外層表為驅動表,先被訪問,如果是in,那么先執行子查詢。子查詢的表大的時候,使用exists可以有效減少總的循環次數來提升速度,當外查詢的表大的時候,使用in可以有效減少對外查詢表循環遍歷來提升速度。

select a.id,a.name from t1 as a where exists(select b.id from t2 as b where a.id=b.id);
select a.id,a.name from t1 as a where a.id in (select b.id from t2 as b);

 

  • sort by&distribute by&cluster by&order by
/******************************************************************
  數據量較小order by即可,Hive中盡量不要使用order by,除非非常確定結果集非常小;
  如果在strict模式下使用order by語句,那么必須要在語句中加上limit關鍵字,因為執行order by只啟動單個reduce,如果排序的結果集過大,那么執行時間會很久;
  實際場景中一般先使用sort by再使用order by效率更高一些,使用distribute和sort進行分組排序,sort by+order by,sort by過程可以設置reducer個數(n),order by過程用n個reduce的輸出文件進行一次全排序,得到最終結果;
  distribute指定map輸出結果是如何分配的,上句中相同的id會被分配到同一個reduce上去處理,然后再通過sort by對各個reduce上的id進行排序(被distribute by設定的字段為KEY,數據會被HASH分發到不同的reducer機器上,然后sort by會對同一個reducer機器上的每組數據進行局部排序)。
******************************************************************/set hive.mapred.mode=nonstrict; (default value / 默認值)
set hive.mapred.mode=strict;

--sort by&distribute by
--sort by只能保證在單個reduce內有序
select * from baidu_click distribute by product_line sort by click desc;
select * from t distribute by id sort by id;

--cluster by(distribute by + sort by替代方案)
--當distribute by和sort by的字段完全一致時,等價於cluster by,但cluster by排序只能是升序排序,不能指定排序規則為ASC或者DESC
--cluster by 和 distribute by 是很相似的,最大的不同是, cluster by 里含有一個分桶的方法
select * from emp cluster by deptno;
select * from emp distribute by deptno sort by deptno; 

--常見兩種高效的排序實現
--可先通過一個group by的子查詢來取一個小的結果集,然后再對這個結果集進行全局排序
select * from (
select id,count(id) as cnt
from t
group by id) as temp
order by temp.cnt;

--高效實現top排序
--先取出各個結果集的top n,再取出全局的top n
select a.id,salary
from (select id,salary from t1 distribute by sort by salary desc limit 10) as temp
order by temp.salary limit 10;

--partition by order by使用distribute by sort by替代
select * from (
select class,grade,row_number() over(partition by class order by grade) as rn from table_name)
where rn<=10;
--使用distribute by sort by替代更高效
select * from (
select class,grade,row_number() over(distribute by class sort by grade) as rn from table_name)
where rn<=10;

 

limit抽樣

--使用limit時限制數據,不會進行全盤掃描,而是根據限制的數據量進行抽樣,帶有reduce的limit會產生不同結果
--設置參數優化(建議使用時手動開啟)
set hive.limit.optimize.enable=true;  --默認false
set hive.limit.row.max.size=xxx;  --最大抽樣數量 默認10萬
set hive.limit.optimize.limit.file=xxx;  --最大抽樣文件數量 默認10 

 

Join on和where的區別

--join on和where在做條件篩選時,on會顯示所有滿足條件和不滿足條件的數據,而where只顯示滿足條件的數據
select t1.a,t2.a from t1 left join on t1.key=t2.key and t2.a>10;  --t1.a 非>10的數據仍會顯示

select t1.a,t2.a from t1 left join on t1.key=t2.key 
where t2.a>10;  --過濾掉前面join后的結果集,只顯示t1.a>10的數據

 

Join優化

在做join連接時,傾向於將小表放入內存中,然后對大表進行map操作,此時join只發生在map階段,即當掃描一個大表時,同時會去小表中查找與之匹配的數據進行連接,而無任何shuffle過程(shuffle往往帶來大量的內存、磁盤、數據傳輸等資源消耗),從而提高執行效率。

--設置mapjoin參數
--開啟方式(如果開啟如下設置,小於閾值大小的表將被自動認為是小表而加載到內存(分布式緩存)中去)
--建議在使用join時都開啟此功能
set hive.auto.convert.join=true
set hive.mapjoin.smalltable.filesize=xxxx  --默認是25M

多表join的連接順序:多表連接中join連接順序,會轉換為多個MR任務,每一個MR任務在Hive中成為join階段(stage),在每一個stage,按照join順序最后一個表盡量是最大的表,因為join前一階段生成的數據會存在於reduce的buffer中,通過stream最后面的表,直接從reduce的buffer中讀取已經緩沖的中間結果數據(中間結果數據可能是join順序中,前面表連接的結果key,數據量相對較小,內存開銷就越小),這樣在與后面的大表進行連接時,只要從buffer中讀取緩存的key,與大表的指定key進行連接,速度會更快,也避免可能的內存緩沖區溢出。

--多個表a、b、c、d表join時,數據量a<b<c<d
select a.val,b.val,c.val,d.val
from a 
join b on a.key=b.key
join c on b.key=c.key
join d on c.key=d.key;

Join key為null容易導致的錯誤:有別於其它傳統關系型數據庫,如在mysql中,join時,join key遇到null值會自動被忽略,但是在Hive中,作為join key的字段比較關聯,null=null是有意義的,且返回ture,但是這個情景是沒有任何實際意義的,且在null值比例很大的情況下,會導致嚴重的數據傾斜,所以在hive中一般需要手動過濾掉null值。

select t1.id,count(t1.id)
 from t1 join t2
  on (t1.id=t2.id and t1.id is not null and t2.id is not null)
group by t1.id;

 

 

Hive分區表使用踩過的坑

為了避免太多小文件,使用了這樣一種數據歸檔的操作處理,跑批按照每日進行分區的表,如pt_dt為分區字段的分區表,按照日期進行歸檔處理,如:

CASE WHEN PT_DT<'2020-01-01' THEN SUBSTR(PT_DT,1,4)||'-01-01'

     WHEN PT_DT>='2020-01-01' AND PT_DT<'2021-01-01' THEN SUBSTAR(PT_DT,1,7)||'-01'

     ESLE PT_DT END AS PT_DT

歸檔操作先將原表(分區表)中的數據插入到一個臨時表(parquet格式的非分區表)中,歸檔到臨時表后,再清除原分區表中的數據,結果出現一個情況,當歸檔pt_dt時間分區數超過100多個時,SQL執行出現job任務報錯,報錯信息為:Error:Failed:Execution Error(state=08s01,code=2),具體報錯:Fatal error occurred when node tried to create too many dynamic partitions.The maximum number of dynamic partitions is controlled by hive.exec.max.dynamic.partitions.pernode.Maximum was set to 100 partitions per node.

經查原因是動態分區屬性導致的,hive.exec.max.dynamic.partitions.pernode=100,表示每個map或reduce可以創建的最大動態分區個數,默認100。這里因為創建的臨時表不是分區表,再insert overwrite到原目標表(分區表)時,只啟動了一個map,即該操作map數為1,reduce數為0,導致一個map任務創建的最大分區數超過了限制,導致報錯。

解決辦法:

1、在SQL執行腳本中設置屬性,調大map的最大分區數據控制,如:hive.exec.max.dynamic.partitions.pernode=400;

2、調整歸檔操作中的case when,調整其可能分區的數量,更大粒度的進行分區。

3、或者在歸檔操作中創建的臨時表也為對應的分區表,以此在將臨時表中的數據插入到目標時,啟動的map數為分區的數量,實際的操作也會是將對應文件進行位置移動,該方法處理后也不會發生該報錯,三種方法均可。

分區表涉及的幾個重要參數設置

--動態分區屬性:設置為true表示開啟動態分區功能(默認為false)
hive.exec.dynamic.partition=true;
 
--動態分區屬性:設置為nonstrict,表示允許所有分區都是動態的(默認為strict)
--設置為strict,表示必須保證至少有一個分區是靜態的
hive.exec.dynamic.partition.mode=strict;
 
--動態分區屬性:每個mapper或reducer可以創建的最大動態分區個數
hive.exec.max.dynamic.partitions.pernode=100;
 
--動態分區屬性:一個動態分區創建語句可以創建的最大動態分區個數
hive.exec.max.dynamic.partitions=1000;
 
--動態分區屬性:全局可以創建的最大文件個數
hive.exec.max.created.files=100000;

 

 

HiveSQL使用注意項

  1. 創建表和刪除表使用if not exists/if exists防止異常;

  2. 分區字段不能出現在建表中,只能出現在partition by中;

  3. 使用具體列名避免使用select *;

  4. where 條件過濾時,!=、<>都會將null值過濾掉,導致實際結果集變小,如果需要保留null值:where (col1 <> 'value' or col1 is null);

  5. group by時,select的列別名不能被group by解析,group by后不能使用別名,因為hive執行解析嚴格按照SQL執行順序,先group by,后select;

  6. Hive不支持UPDATE操作,只能drop再insert;

  7. hive創建視圖和其它數據庫創建視圖無異;

  8. hive int與string類型,null底層默認存儲為\N,查詢顯示為null,導出文件會以存儲格式導出,需要注意。若導出為null,存儲的字符串就是null字符串而非null值;SQL中null代表空值, 值得警惕的是, 在HiveQL中String類型的字段若是空(empty)字符串, 即長度為0, 那么對它進行IS NULL的判斷結果是False;

  9. 分號是SQL語句結束標記,在HiveQL中也是,但是在HiveSQL中,對分號的識別沒有那么智慧,在DBeaver SQL IDE中也會出現因為加了;導致SQL報錯的情況。另外字符';'使用需要轉義,如:select concat(key,concat(';',key)); 在Hive中會報錯,應使用分號的八進制的ASCII碼進行轉義,應寫成:select concat(key,concat('\073',key));

  10. hive支持嵌入mapreduce程序,來處理復雜的邏輯,但一般不使用,為了維護方便,類似桶表一般也不使用;

  11. 如何查看Hive的屬性設置情況,如:set hive.mapred.mode; --hive.mapred.mode=nonstrict,要注意的是strict模式也會限制分區表的查詢,解決方案是必須指定分區;

  12. Hive的Join on條件只支持等值連接(如:t1.key=t2.key),where條件則無限制。hive從2.2.0版本開始,join on也支持復雜表達式,包括非等值連接;

  13. 窗口函數row_number()使用不當問題,在使用row_number()時,over()里面的分組以及排序晚於where、group by、order by執行。partition by用於給結果集分組,在hive中使用row_number()窗口函數時,如果使用了partition參數,需要特別注意partition by的key如果太多,超過了集群的限制,則會報錯。如:select empno,deptno,salary,row_number(partition by deptno order by salary desc) rank from employee;

  14. create table as select * from other_table 容易引起風險問題,如果源表某個字段修改了字段類型,如col1本是int型,修改為了string,那么通過這種方式創建的表該字段還是會被推斷為int,這就存在了風險隱患,建議使用:drop table if exists table_name; create table t_new as select * from table_old where 1=2;或者:create table t_new like t_old;或者使用標准表創建再插入數據。 


免責聲明!

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



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