Hive基礎二(join原理和機制,join的幾種類型,數據傾斜簡單處理)

相關鏈接: 
Hive基礎一(數據庫,表,分區表,視圖,導入導出數據) 
Hive基礎二(join原理和機制,join的幾種類型,數據傾斜簡單處理) 
Hive基礎三(查詢中常用的語法)

【注意】有些語句會報錯,這是因爲hive版本問題,比如有些join中的outer不能省略,等等。

一,Hive中join的原理和機制
Hive中的Join可分爲Common Join(Reduce階段完成join)和Map Join(Map階段完成join)。

Hive Common Join 
如果不指定MapJoin或者不符合MapJoin的條件,那麼Hive解析器會默認把執行Common Join,即在Reduce階段完成join。整個過程包含Map、Shuffle、Reduce階段。

Map階段 
讀取源表的數據,Map輸出時候以Join on條件中的列爲key,如果Join有多個關聯鍵,則以這些關聯鍵的組合作爲key;Map輸出的value爲join之後所關心的(select或者where中需要用到的)列,同時在value中還會包含表的Tag信息,用於標明此value對應哪個表。
Shuffle階段 
根據key的值進行hash,並將key/value按照hash值推送至不同的reduce中,這樣確保兩個表中相同的key位於同一個reduce中。
Reduce階段 
根據key的值完成join操作,期間通過Tag來識別不同表中的數據。
以下面的HQL爲例,圖解其過程:

SELECT a.id,a.dept,b.age 
FROM a join b 
ON (a.id = b.id);
1
2
3


Hive Map Join 
MapJoin通常用於一個很小的表和一個大表進行join的場景,具體小表有多小,由參數hive.mapjoin.smalltable.filesize來決定,默認值爲25M。滿足條件的話Hive在執行時候會自動轉化爲MapJoin,或使用hint提示 /*+ mapjoin(table) */執行MapJoin。 


如上圖中的流程,首先Task A在客戶端本地執行,負責掃描小表b的數據,將其轉換成一個HashTable的數據結構,並寫入本地的文件中,之後將該文件加載到DistributeCache中。 
接下來的Task B任務是一個沒有Reduce的MapReduce,啓動MapTasks掃描大表a,在Map階段,根據a的每一條記錄去和DistributeCache中b表對應的HashTable關聯,並直接輸出結果,因爲沒有Reduce,所以有多少個Map Task,就有多少個結果文件。 
注意:Map JOIN不適合FULL/RIGHT OUTER JOIN。

二,join的幾種類型
Hive中除了支持和傳統數據庫中一樣的內關聯(JOIN)、左關聯(LEFT JOIN)、右關聯(RIGHT JOIN)、全關聯(FULL JOIN),還支持左半關聯(LEFT SEMI JOIN)。 
注意:Hive中Join的關聯鍵必須在ON()中指定,不能在Where中指定。 
數據準備:

hive> desc lxw1234_a;
OK
id                      string                                      
name                    string                                      
Time taken: 0.094 seconds, Fetched: 2 row(s)
hive> select * from lxw1234_a;
OK
1       zhangsan
2       lisi
3       wangwu
Time taken: 0.116 seconds, Fetched: 3 row(s)
hive> desc lxw1234_b;
OK
id                      string                                      
age                     int                                         
Time taken: 0.159 seconds, Fetched: 2 row(s)
hive> select * from lxw1234_b;
OK
1       30
2       29
4       21
Time taken: 0.09 seconds, Fetched: 3 row(s)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
內關聯(JOIN) 
常規join,類似交集,只顯示關聯成功的行。

SELECT a.id, a.name, b.age 
FROM lxw1234_a a 
join lxw1234_b b 
ON (a.id = b.id);
--執行結果
1       zhangsan        30
2       lisi        29
1
2
3
4
5
6
7
8
左外關聯(LEFT [OUTER] JOIN) 
以LEFT [OUTER] JOIN關鍵字前面的表作爲主表,和其他表進行關聯,返回記錄和主表的記錄數一致,關聯不上的字段置爲NULL。 
是否指定OUTER關鍵字,貌似對查詢結果無影響。

SELECT a.id, a.name, b.age 
FROM lxw1234_a a 
left join lxw1234_b b 
ON (a.id = b.id);
--執行結果:
1   zhangsan   30
2   lisi        29
3   wangwu    NULL
1
2
3
4
5
6
7
8
9
右外關聯(RIGHT [OUTER] JOIN) 
和左外關聯相反,以RIGTH [OUTER] JOIN關鍵詞後面的表作爲主表,和前面的表做關聯,返回記錄數和主表一致,關聯不上的字段爲NULL。 
是否指定OUTER關鍵字,貌似對查詢結果無影響。

SELECT a.id, a.name, b.age 
FROM lxw1234_a a 
RIGHT OUTER JOIN lxw1234_b b 
ON (a.id = b.id);
--執行結果:
1          zhangsan        30
2          lisi        29
NULL       NULL        21
1
2
3
4
5
6
7
8
全外關聯(FULL [OUTER] JOIN) 
以兩個表的記錄爲基準,返回兩個表的所有記錄,類似並集,關聯不上的字段爲NULL。 
是否指定OUTER關鍵字,貌似對查詢結果無影響。

SELECT a.id, a.name, b.age 
FROM lxw1234_a a 
FULL OUTER JOIN lxw1234_b b 
ON (a.id = b.id);
--執行結果:
1       zhangsan            30
2       lisi            29
3       wangwu          NULL
NULL    NULL            21
1
2
3
4
5
6
7
8
9
10
左半連接(LEFT SEMI JOIN) 
以LEFT SEMI JOIN關鍵字前面的表爲主表,返回主表的KEY也在副表中的記錄。相當於IN或EXISTS的作用。左半連接的限制是右側的表只能出現在ON子句中,不能出現在WHERE或者SELECT子句中。 
注意,left semi join和join並不是等價的,比如當左表1條數據可以關聯右表2條數據時,此時left semi join只顯示1條數據(因爲只要左表的key在右表存在就行,不用管幾條),join卻顯示兩條數據(關聯到的都顯示),好好體會一下。

SELECT a.id, a.name 
FROM lxw1234_a a 
LEFT SEMI JOIN lxw1234_b b 
ON (a.id = b.id);
--執行結果:
1       zhangsan
2       lisi
--等價於:
SELECT a.id, a.name 
FROM lxw1234_a a 
WHERE a.id IN (SELECT id FROM lxw1234_b);
1
2
3
4
5
6
7
8
9
10
11
三,join語句需要注意的地方
首先是Hive中的連接查詢只支持相等連接而不支持不等連接查詢:

//有效的連接查詢,相等連接查詢  
SELECT a.* FROM a JOIN b ON (a.id = b.id AND a.department = b.department)  
//無效的連接查詢,Hive不支持不等連接查詢  
SELECT a.* FROM a JOIN b ON (a.id <> b.id)   
1
2
3
4
如果每個表都只使用相同的列join連接,Hive將只生成一個map/reduce作業;如果一個表使用了兩個以上的字段,則會生成2個以上的mr任務:

//由於join子句中只使用了表b的key1列,該查詢轉換爲一個作業  
SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1)  
//由於表b的key1列用在第一個join子句中,key2列用在第二個join子句中,該查詢被轉換爲兩個作業,
//第一個作業執行表a和b的連接查詢,第二個作業將第一個作業的結果與第二個join子句進行連接查詢  
SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2)  
1
2
3
4
5
Join連接的順序是不可以交換的,無論是LEFT還是RIGHT連接都是左結合的。

四,hive數據傾斜的簡單處理
• 數據傾斜症狀: 
任務長時間維持在99%(或100%); 
查看任務監控頁面,發現只有少量(1個或幾個)reduce子任務未完成; 
本地讀寫數據量很大。 
• 原因: 
key分佈不均勻; 
業務數據本身特點。 
• 導致數據傾斜的操作: 
GROUP BY, COUNT DISTINCT(),join

這裏列出幾個常用解決辦法:

參數hive.groupby.skewindata = true 
似乎是解決數據傾斜的萬能鑰匙,查詢計劃會有兩個 MR Job,第一個 MR Job 中,Map 的輸出結果集合會隨機分佈到 Reduce 中,每個Reduce做部分聚合操作,並輸出結果,這樣處理的結果是相同的 Group By Key 有可能被分發到不同的 Reduce中,從而達到負載均衡的目的;第二個 MR Job 再根據預處理的數據結果按照 Group By Key 分佈到 Reduce中(這個過程可以保證相同的 Group By Key 被分佈到同一個 Reduce 中),最後完成最終的聚合操作。

使用COUNT DISTINCT造成的數據傾斜

如果某一個值的記錄特別多,可以先把該值過濾掉,在最後單獨處理。比如某一天的IMEI值爲’lxw1234’的特別多,當我要統計總的IMEI數,可以先統計不爲’lxw1234’的,之後再加1。

SELECT CAST(COUNT(DISTINCT imei)+1 AS bigint)
FROM lxw1234 where pt = '2012-05-28'
AND imei <> 'lxw1234' ;
1
2
3
數據量大的情況下,由於COUNT DISTINCT操作需要用一個Reduce Task來完成,這一個Reduce需要處理的數據量太大,就會導致整個Job很難完成,一般COUNT DISTINCT使用先GROUP BY再COUNT的方式替換:

SELECT day, COUNT(DISTINCT id) AS uv
FROM lxw1234
GROUP BY day;
--可以轉換成:
SELECT day, COUNT(id) AS uv
FROM (SELECT day,id FROM lxw1234 GROUP BY day,id) a
GROUP BY day;
1
2
3
4
5
6
7
雖然會多用一個Job來完成,但在數據量大的情況下,這個絕對是值得的。

使用JOIN引起的數據傾斜

表連接順序優化: 
多表連接時,儘量小表在前,大表在後。因爲JOIN前一階段生成的數據會存在於Reducer的buffer中,小表數據量小,內存開銷就小,與後面的大表進行連接時,只需要從buffer中讀取緩存的Key,與大表中的指定Key進行連接,速度會更快,也可能避免內存緩衝區溢出。
where優化: 
Join出現在WHERR子句之前。因此如果想在join前進行過濾,限制條件應該出現在JOIN子句中,而不是where中:

SELECT a.val, b.val FROM a LEFT OUTER JOIN b 
ON (a.key=b.key)  
WHERE a.ds='2009-07-07' AND b.ds='2009-07-07';
//當該左外連接在a中發現key而在b中沒有發現key時,b中的列將爲null,包括分區列ds,後邊的where就沒有用了。
//下面的語句將會提前根據條件過濾:
SELECT a.val, b.val FROM a LEFT OUTER JOIN b  
ON (a.key=b.key AND b.ds='2009-07-07' AND a.ds='2009-07-07');
1
2
3
4
5
6
7
關聯鍵存在大量空值: 
在SQL標準中,任何對NULL的操作(如數值比較,字符串操作等)結果都爲NULL。Hive對NULL值的處理與其基本一致,除了JOIN時的特殊邏輯:Hive的JOIN中作爲JOIN Key的字段比較,NULL=NULL是有意義的,且返回值爲True。 
所以需要改寫查詢手動過濾NULL值的情況,操作如下:

SELECT user.uid,count(user.uid) FROM class JOIN user 
ON(class.uid = user.uid and class.uid IS NOT NULL and user.uid IS NOT NULL) 
GROUP BY user.uid;
1
2
3
不同數據類型的字段關聯: 
轉換爲同一數據類型之後再做關聯。

原文來自:http://lxw1234.com/archives/2015/07/365.htm
————————————————
版權聲明:本文爲CSDN博主「login_sonata」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/login_sonata/article/details/75000766

發佈了15 篇原創文章 · 獲贊 11 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章