Hive學習筆記(五)—Hive連接優化

1. Join 的基本原理

大家都知道,Hive 會將所有的 SQL 查詢轉化爲 Map/Reduce 作業運行於 Hadoop 集羣之上。在這裏簡要介紹 Hive 將 Join 轉化爲 Map/Reduce 的基本原理。

假定有 user 和 order 兩張表,分別如下:

user 表:

sid name
1 apple
2 orange

order 表:

uid orderid
1 1001
1 1002
2 1003

現在想做 student 和 sc 兩張表上的連接操作:

SELECT
  u.name,
  o.orderid
FROM user u
JOIN order o ON u.uid = o.uid;

Hive 是利用 hadoop 的 Map/Reduce 計算框架執行上述查詢的呢??

Hive 會將出現在連接條件 ON 之後的所有字段作爲 Map 輸出的 key,將所需的字段作爲 value,構建(key, value),同時爲每張表打上不同的標記 tag,輸出到 Reduce 端。在 Reduce 端,根據 tag 區分參與連接的表,實現連接操作。

我們使用下圖來模擬這個過程:

在 Map 端分別掃描 user 和 order 的兩張表。對於 user 表,在連接條件 ON 之後的字段爲 uid,所以以 uid 作爲 Map 輸出的 key,在 SELECT 語句中還需要 name 字段,所以 name 字段作爲 value 的一部分,同時爲 user 表賦予標記 tag=1,這樣處理 user 表的 mapper 地輸出形式爲:(uid, “1” + name)。類似的,處理 order 表的 mapper 地輸出形式爲:(uid, “2” + orderid),注意,order 表的標記爲 2。

具有相同 uid 的地(key, value)字段在 reduce 端“集合”,根據 value 中 tag 字段區分來自不同表的數據,使用兩層循環完成連接操作。

上面就是將 Join 操作轉換爲 Map/Reduce 作業的基本原理: 在 map 端掃描表,在 reduce 端完成連接操作。

2. Hive 中的各種 Join

寫在前面的話:”Hive 不支持非等值連接”

我們使用例子講述各種 Join 的區別。假設 my_user 和 my_order 兩張表的數據變爲:

my_user 表:

uid name
1 apple
2 orange
3 banana

my_order 表:

uid orderid
1 1001
1 1002
2 1003
2 1003
4 2001

注意,my_order 中有一條重複記錄。

“不要考慮例子在現實中的意義,這裏這是爲了演示各種 JOIN 的區別”

2.1 INNER JOIN

INNER JOIN,又稱“內連接”,執行 INNER JOIN 時,只有兩個表中都有滿足連接條件的記錄時纔會保留數據。執行以下語句:

SELECT
  u.name,
  o.orderid
FROM my_user u
JOIN my_order o ON u.uid = o.uid;

結果爲:

name orderid
apple 1001
apple 1002
orange 1003
orange 1003

因爲表 my_order 中又重複記錄,所以結果中也有重複記錄。

2.2 LEFT OUTER JOIN

LEFT OUTER JOIN(左外連接),JOIN 操作符左邊表中符合 WHERE 條件的所有記錄都會被保留,JOIN 操作符右邊表中如果沒有符合 ON 後面連接條件的記錄,則從右邊表中選出的列爲 NULL。

執行以下語句:

SELECT
  u.name,
  o.orderid
FROM my_user u
LEFT OUTER JOIN my_order o ON u.uid = o.uid;

結果爲:

name orderid
apple 1001
apple 1002
orange 1003
orange 1003
banana NULL

這裏由於沒有 WHERE 條件,所以左邊表 my_user 中的記錄都被保留,對於 uid=3 的記錄,在右邊表 my_order 中沒有相應記錄,所以 orderid 爲 NULL。

2.3 RIGHT OUTER JOIN

RIGHT OUTER JOIN(右外連接),LEFT OUTER JOIN 相對,JOIN 操作符右邊表中符合 WHERE 條件的所有記錄都會被保留,JOIN 操作符左邊表中如果沒有符合 ON 後面連接條件的記錄,則從左邊表中選出的列爲 NULL。

SELECT
  u.name,
  o.orderid
FROM my_user u
RIGHT OUTER JOIN my_order o ON u.uid = o.uid;

執行上面 SQL 語句的結果爲:

name orderid
apple 1001
apple 1002
orange 1003
orange 1003
NULL 2001

由於左表 my_user 中不存在 uid=4 的記錄,所以 orderid=2001 的記錄對應的 name 爲 NULL。

2.3 FULL OUTER JOIN

結合上面的 LEFT OUTER JOIN 和 RIGHT OUTER JOIN,很容易想到 FULL OUTER JOIN 的運行機制:保留滿足 WHERE 條件的兩個表的數據,沒有符合連接條件的字段使用 NULL 填充。來看一個例子:

SELECT
  u.name,
  o.orderid
FROM my_user u
FULL OUTER JOIN my_order o ON u.uid = o.uid;

執行結果爲:

name orderid
apple 1001
apple 1002
orange 1003
orange 1003
banana NULL
NULL 2001

原因不再解釋,請自行思考。

2.4 LEFT SMEI JOIN

在早期的 Hive 版本中,不是 IN 關鍵字,可以使用 LEFT SEMI JOIN 實現類似的功能。

LEFT SEMI JOIN(左半開連接)返回左邊表的記錄,前提是右邊表具有滿足 ON 連接條件的記錄。

先來看一個例子:

SELECT
  *
FROM my_user u
LEFT SEMI JOIN my_order o ON u.uid = o.uid;

執行結果爲:

uid name
1 apple
2 orange

雖然 SELECT 中使用’*‘,但是隻返回了左表 my_user 的列,而且重複的記錄沒有返回(重複記錄在 my_order 表中)

需要強調的是:

  • 在 LEFT SEMI JOIN 中,SELECT 中不允許出現右表中的列
  • 對於左表中的一條記錄,在右表表中一旦找到匹配記錄就停止掃描

3. JOIN 優化

現實環境中會進行大量的表連接操作,而且表連接操作通常會耗費很懂時間。因此掌握一些基本的 JOIN 優化方法成爲熟練運用 Hive、提高工作效率的基本手段。下面討論一些常用的 JOIN 優化方法。

3.1 MAP-JOIN

本文一開始介紹了 Hive 中 JOIN 的基本原理,這種 JOIN 沒有數據大小的限制,理論上可以用於任何情形。但缺點是:需要 map 端和 reduce 端兩個階段,而且 JOIN 操作是在 reduce 端完成的,稱爲 reduce side join。

那麼,能否省略 reduce 端,直接在 map 端執行的“map side join”操作呢??答案是,可以的。

但有個條件,就是:連接的表中必須有一個小表足以放到每個 mapper 所在的機器的內存中。

下圖展示了 map side join 的原理。

從上圖中可以看出,每個 mapper 都會拿到小表的一個副本,然後每個 mapper 掃描大表中的一部分數據,與各自的小表副本完成連接操作,這樣就可以在 map 端完成連接操作。

那多大的表纔算是“小表”呢??

默認情況下,25M 以下的表是“小表”,該屬性由hive.smalltable.filesize決定。

有兩種方法使用 map side join:

  • 直接在 SELECT 語句中指定“小表”,語法是/+MAPJOIN (tbl)/,其中 tbl 就是要複製到每個 mapper 中去的小表。例如:
SELECT
  /*+ MAPJOIN(my_order)*/
  u.name,
  o.orderid
FROM my_user u
LEFT OUTER JOIN my_order o ON u.uid = o.uid;
  • 設置hive.auto.convert.join = true,這樣 hive 會自動判斷當前的 join 操作是否合適做 map join,主要是找 join 的兩個表中有沒有小表。

但 JOIN 的兩個表都不是“小表”的時候該怎麼辦呢??這就需要 BUCKET MAP JOIN 上場了。

3.2 BUCKET MAP JOIN

Map side join 固然得人心,但終會有“小表”條件不滿足的時候。這就需要 bucket map join 了。

Bucket map join 需要待連接的兩個表在連接字段上進行分桶(每個分桶對應 hdfs 上的一個文件),而且小表的桶數需要時大表桶數的倍數。建立分桶表的例子:

CREATE TABLE my_user
(
  uid INT,
  name STRING
)
CLUSTERED BY (uid) into 32 buckets
STORED AS TEXTFILE;

這樣,my_user 表就對應 32 個桶,數據根據 uid 的 hash value 與 32 取餘,然後被分發導不同的桶中。

如果兩個表在連接字段上分桶,則可以執行 bucket map join 了。具體的:

  1. 設置屬性hive.optimize.bucketmapjoin= true控制 hive 執行 bucket map join;
  2. 對小表的每個分桶文件建立一個 hashtable,並分發到所有做連接的 map 端;
  3. map 端接受了 N(N 爲小表分桶的個數) 個小表的 hashtable,做連接 操作的時候,只需要將小表的一個 hashtable 放入內存即可,然後將大表的對應的 split 拿出來進行連接,所以其內存限制爲小表中最大的那個 hashtable 的大小

3.3 SORT MERGE BUCKET MAP JOIN

對於 bucket map join 中的兩個表,如果每個桶內分區字段也是有序的,則還可以進行 sort merge bucket map join。對於那個的建表語句爲:

CREATE TABLE my_user
(
  uid INT,
  name STRING
)
CLUSTERED BY (uid) SORTED BY (uid) into 32 buckets
STORED AS TEXTFILE;

這樣一來當兩邊 bucket 要做局部 join 的時候,只需要用類似 merge sort 算法中的 merge 操作一樣把兩個 bucket 順序遍歷一遍即可完成,這樣甚至都不用把一個 bucket 完整的加載成 hashtable,而且可以做全連接操作。

進行 sort merge bucket map join 時,需要設置的屬性爲:

1.  `set hive.optimize.bucketmapjoin=  true;`
2.  `set hive.optimize.bucketmapjoin.sortedmerge =  true;`
3.  `set hive.input.format = org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;`

4.各種 JOIN 對比

JOIN類型 優點 缺點
COMMON JOIN 可以完成各種 JOIN 操作,不受表大小和表格式的限制 無法只在 map 端完成 JOIN 操作,耗時長,佔用更多地網絡資源
MAP JOIN 可以在 map 端完成 JOIN 操作,執行時間短 待連接的兩個表必須有一個“小表”,“小表”必須加載內存中
BUCKET MAP JOIN 可以完成 MAP JOIN,不受“小表”限制 表必須分桶,做連接時小表分桶對應 hashtable 需要加載到內存
SORT MERGE BUCKET MAP JOIN 執行時間短,可以做全連接,幾乎不受內存限制 表必須分桶,而且桶內數據有序

參考

本文轉載自:http://datavalley.github.io/2015/10/25/Hive%E4%B9%8BJOIN%E5%8F%8AJOIN%E4%BC%98%E5%8C%96

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