Hive的分桶和取樣
Hive除了有分割槽(Partition) ,還有分桶(Bucket) ,上一篇文章《Hive的分割槽》中介紹了分割槽,本文接著介紹分桶,以及和分桶經常一起使用的取樣(Sampling) 。
其實不管是分割槽還是分桶都是為了更好的管理資料。分割槽將表的資料分到不同的目錄儲存,從而在查詢的時候可以通過where條件過濾一部分資料,減小查詢的資料量從而提高效能。但分割槽的這種機制往往在資料符合以下條件時才會表現的比較好:
- 分割槽數目為有限個 :一般也不能太大,不然太多的檔案和目錄對於HDFS的NameNode會造成比較大的記憶體壓力。
- 各個分割槽的資料量比較均衡 :這個好理解,如果90%的資料跑到一個分割槽去,那分割槽的意義就不是很大了。
然而,並非所有場景都是符合上面兩條的。比如比較常見的按照國家等地理位置去分割槽的時候,可能幾個大國的資料就佔了總資料量的百分之七八十,而剩下的所有國家只佔了百分之二三十,也就是不符合上面的第二條。這個時候分割槽的好處就大打折扣了。為了克服這個問題,Hive增加了分桶的機制。我們先介紹一些分桶相關的基礎概念,然後通過例子再做說明。
1. 分桶
1.1 基礎理論
分桶的概念其實比較簡單:指定桶的個數,選中表中的若干個列,通過對資料中這些列的值做雜湊決定資料應該儲存到哪個桶裡面去,公式表示為:bucket num = hash_function(bucketing_column) mod num_buckets ,不同的資料型別雜湊函式不同。關於分桶還有以下一些關鍵點:
CLUSTERED BY
分桶也可以帶來如下一些好處:
- 分桶表比不分桶表的取樣(samping)效率高 。Hive提供了取樣機制,用於我們從大量資料中提取一部分來做測試或除錯,而分桶可以幫助我們更好的取樣(見後面的例子)。
- Map過程中的join會更快 。主要有兩方面原因:1. 分桶後,檔案大小均衡。2. join的時候左表可以計算對應的右表符合條件的行在哪個桶裡面,直接去獲取資料即可。
- 和分割槽一樣,分桶後查詢資料會更快 。
- 我們在桶內做排序,這樣可以讓Map裡面的join操作更快速。
下面我們看一個例子。
1.2 例子展示
場景需求: 還是使用上篇文章裡面的使用者資料36078bf2" rel="nofollow,noindex" target="_blank">UserRecords.txt ,但這次只使用國家作為分割槽列,州做雜湊列,也就是分桶列,並且桶內按照城市名排序。
和分割槽表一樣,分桶表也不能使用LOAD DATA
的方式匯入資料,所以我們還是像前文一樣,先把資料導到一張臨時表temp_user
裡面,這個步驟省略。如果不清楚的,請看上一篇文章(Hive的分割槽)。然後我們建立分桶表:
# 語法 CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name [(col_name data_type [COMMENT col_comment], ... [constraint_specification])] [COMMENT table_comment] [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS] # 建立bucketed_user表 CREATE TABLE bucketed_user ( firstnameVARCHAR(64), lastnameVARCHAR(64), addressSTRING, cityVARCHAR(64), stateVARCHAR(64), postSTRING, phone1VARCHAR(64), phone2STRING, emailSTRING, webSTRING ) COMMENT 'A bucketed sorted user table' PARTITIONED BY(country VARCHAR(64)) CLUSTERED BY(state) SORTED BY(city) INTO 32 BUCKETS;
我們使用可以看到,不像分割槽列是一個虛擬列,分桶列是表中的欄位。在插入欄位之前,我們先介紹兩個和分桶相關的配置項:
- hive.enforce.bucketing :如果建表時設定了分桶,插入資料的時候是否強制分桶。Hive 2.x版本之前預設為false,之後去掉了該配置,並將預設情況改為true。見HIVE-12331 .
- hive.enforce.sorting :如果建表時設定了分桶,插入資料的時候是排序,Hive 2.x版本之前預設為false,之後去掉了該配置,並將預設情況改為true。見HIVE-12331 .
在Hive 2.x之前,在定義表結構時通過CLUSTERED BY
指定分桶資訊,然後在插入資料的時候如果設定hive.enforce.bucketing
為true,則會按照表定義裡面的桶個數進行自動分桶(dynamic bucket);如果不設定為true,使用者也可以指定如何分桶(主要是指定reduce的個數
set mapred.reduce.tasks=xx
)。這樣導致的問題就是表結構裡面雖然定義了分桶資訊,但實際插入資料的時候可能並沒有分桶或者分桶方式與表結構裡面定義的不一致。這樣後續操作的時候有了很多不確定性,容易產生各種問題,所以在2.x版本之後,去掉了該項配置,直接將預設情況設定為了true。如果你用的Hive是2.x之前的版本,使用分桶的時候記得將這兩個選項置為true。另外需要說明的是桶的個數決定了reduce的個數。
下面我們來插入資料:
# 因為我們使用了動態分割槽,所以和前文一樣,設定動態分割槽相關的引數,引數含義見《Hive的分割槽》一文 set hive.exec.dynamic.partition=true; set hive.exec.dynamic.partition.mode=nonstrict; set hive.exec.max.dynamic.partitions.pernode=1000; # 覆蓋式插入資料 INSERT OVERWRITE TABLE bucketed_user PARTITION(country) SELECT firstname, lastname, address, city, state, post, phone1, phone2, email, web, country FROM temp_user;
執行插入語句的時候,大家可以看列印的日誌,一共有32個reducers,和桶的個數一致。插入完成之後我們來看HDFS上面的資料:
➜~ hadoop fs -ls -R /user/hive/warehouse/bucketed_user drwxrwxr-x- allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000000_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000001_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000002_0 -rwxrwxr-x1 allan supergroup806 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000003_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000004_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000005_0 -rwxrwxr-x1 allan supergroup17140 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000006_0 -rwxrwxr-x1 allan supergroup950 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000007_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000008_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000009_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000010_0 -rwxrwxr-x1 allan supergroup11314 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000011_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000012_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000013_0 -rwxrwxr-x1 allan supergroup4427 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000014_0 -rwxrwxr-x1 allan supergroup6132 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000015_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000016_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000017_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000018_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000019_0 -rwxrwxr-x1 allan supergroup12405 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000020_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000021_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000022_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000023_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000024_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000025_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000026_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000027_0 -rwxrwxr-x1 allan supergroup15262 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000028_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000029_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000030_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000031_0 drwxrwxr-x- allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=CA -rwxrwxr-x1 allan supergroup ……省略後續輸出……
可以看到每個分割槽內的資料被分成了32份,因為資料量太小,所以很多桶內是空的。如果資料量大的話,各個桶的資料量會比較均衡。
2. 取樣
從大量資料中取樣得到少量資料進行測試或者除錯是資料分析中非常常見的操作,拋開業務在資料庫我們可以使用LIMIT
語句實現該功能,但Hive提供了一個TABLESAMPLE
語法用來更好的實現取樣,TABLESAMPLE
語句可以放到任何FROM語句中。目前支援兩種取樣方式:基於表的取樣Sampling Bucketized Table
和基於塊的取樣Block Sampling
。
2.1 Sampling Bucketized Table
基於分桶表的取樣的語法格式如下:
table_sample: TABLESAMPLE (BUCKET x OUT OF y [ON colname] [table_alias])
- colname 表示基於表中哪個欄位進行取樣,這個欄位可以是分桶列中的某個欄位,也可以不是;還可以使用rand() 表示基於整行而不是單獨某個欄位進行取樣。
-
x out of y:表示要取哪些桶的資料。假設表有32個桶:
- 3 out of 32 表示總共取32/32=1 個桶的資料,取的是第3個桶的資料;
- 3 out of 16 表示總共取32/16=2 個桶的資料,分別取第3個、第19(1+16)個桶的資料;
- 3 out of 64 表示總共取32/64=0.5 個桶的資料,取第3個桶一半的資料。
- table_alias 給表起的別名,類似SQL/">MySQL的AS。
一些例子:
# 基於state欄位取樣 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(BUCKET 3 OUT OF 32 ON state); +-------------+----------+--------------+---------------------------------+ |firstname| country|state|city| +-------------+----------+--------------+---------------------------------+ | Marleen| CA| BC| Abbotsford| | Carole| CA| BC| Abbotsford| | Lasandra| CA| BC| Abbotsford| | Yvette| CA| AB| Big Valley| | Annamae| CA| BC| Burnaby| | Adela| CA| BC| Burnaby| ……省略…… | Shelia| UK| Cumbria| Silloth-on-Solway| | Mauricio| UK| Cumbria| Walney North Ward| | Quentin| US| MN| Burnsville| | Cyndy| US| MN| Burnsville| | Novella| US| HI| Hilo| | Brandon| US| HI| Honolulu| | Angella| US| HI| Honolulu| | Fatima| US| MN| Hopkins| | Skye| US| MN| Minneapolis| | Rodolfo| US| MN| Northfield| | Rolande| US| HI| Pearl City| | Chantell| US| MN| Saint Paul| | Matthew| US| MN| Shakopee| +-------------+----------+--------------+---------------------------------+ 131 rows selected (1.06 seconds) # 使用rand()取樣 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(BUCKET 3 OUT OF 32 ON rand()); +-------------+----------+---------------------+---------------------------------+ |firstname| country|state|city| +-------------+----------+---------------------+---------------------------------+ | Nenita| AU| NS| Botany| | Aide| AU| NS| Rhodes| | Hester| AU| NS| The Risk| | Annita| AU| NT| Karama| | Mariko| AU| WA| Hamel| | Emelda| AU| WA| Nedlands| | Leatha| AU| WA| Two Rocks| | Kenny| AU| TA| Nicholls Rivulet| | Eveline| AU| VI| Camberwell West| ……省略…… | Martina| US| FL| Orlando| | Billye| US| MS| Pearl| | Timothy| US| NY| Staten Island| | Pamella| US| CO| Denver| | Minna| US| PA| Kulpsville| | Fabiola| US| PA| York| | Junita| US| NJ| Cedar Grove| | Helaine| US| NJ| Jersey City| | Heike| US| NJ| Little Falls| | Eladia| US| NJ| Ramsey| | Felicidad| US| NJ| Riverton| +-------------+----------+---------------------+---------------------------------+ 58 rows selected (0.595 seconds)
需要注意的是這種取樣方式並不要求表一定要是分桶的 ,如果沒有分桶或者分桶了但取樣的欄位不在分桶欄位裡面,那也是可以正常取樣的,只不過取樣時會掃描全表資料,不是很高效而已。所以大多數情況這種取樣方式都是和分桶一起使用的,取樣的欄位就是分桶的欄位,這樣取樣時只掃描對應的桶就行,可以大大提高效率。
2.2 Block Sampling
基於塊的取樣是後來新增的一項功能(從Hive 0.8版本開始,見HIVE-2121 ),這裡的塊指的是HDFS的Block。目前有三種方式,基本語法為:
# 基於百分比 block_sample: TABLESAMPLE (n PERCENT) # 基於大小 block_sample: TABLESAMPLE (ByteLengthLiteral) ByteLengthLiteral : (Digit)+ ('b' | 'B' | 'k' | 'K' | 'm' | 'M' | 'g' | 'G') # 基於行數 block_sample: TABLESAMPLE (n ROWS)
基於百分比取樣和基於大小的取樣實質是一樣的(見HIVE-3401 ),這兩種方式目前不支援一些壓縮的格式。如果取樣失敗了,就會返回整個表或者分割槽的資料。需要注意的是,因為是基於塊取樣的,所以最小的取樣單位就是HDFS的一個block,也就是說返回的資料可能會比實際的資料多。比如%1的資料是100MB,但HDFS的一個block是256MB,那取樣得到的資料將是256MB。一個示例:
# 取樣0.1%的資料 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(0.1 PERCENT) sampled_bucketed_user; +------------+----------+-------------+---------+ | firstname| country|state|city| +------------+----------+-------------+---------+ | Soledad| AU| AC| Barton| | Darell| CA| ON| Ajax| | Allene| UK| Derbyshire| Barlow| | Devorah| US| NM| Clovis| +------------+----------+-------------+---------+ 4 rows selected (0.467 seconds) # 取樣100MB資料 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(100B) sampled_bucketed_user; +------------+----------+------------------+--------------------+ | firstname| country|state|city| +------------+----------+------------------+--------------------+ | Santos| AU| NS| Allworth| | Avery| CA| NS| Amherst| | Lewis| UK| South Yorkshire| Central Ward| | Weldon| US| IL| Arlington Heights| +------------+----------+------------------+--------------------+ 4 rows selected (0.187 seconds)
如果我們想保證每次取樣的資料一樣,可以設定種子:
set hive.sample.seednumber=<INTEGER>;
預設值是0,比如我們改為100,再採一次樣:
0: jdbc:hive2://localhost:10000> set hive.sample.seednumber=100; No rows affected (0.006 seconds) 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(0.1 PERCENT) sampled_bucketed_user; +------------+----------+------------------+--------------------+ | firstname| country|state|city| +------------+----------+------------------+--------------------+ | Santos| AU| NS| Allworth| | Avery| CA| NS| Amherst| | Lewis| UK| South Yorkshire| Central Ward| | Weldon| US| IL| Arlington Heights| +------------+----------+------------------+--------------------+ 4 rows selected (0.26 seconds)
基於行數的取樣和前面兩種方式不太一樣:
- 沒有資料格式的限制;
- 採集的條數n會在每個分片(split)都執行一次,所以採集到的總條數和輸入的分片數也有關係。
# 從輸入的每個分片從採5條資料(這裡只有一個分片) 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(5 ROWS) sampled_bucketed_user; +------------+----------+--------+-----------------+ | firstname| country| state|city| +------------+----------+--------+-----------------+ | Soledad| AU| AC| Barton| | Annamae| AU| AC| Civic Square| | Katheryn| AU| AC| Fyshwick| | Roy| AU| AC| Red Hill| | Jamie| AU| AC| Tuggeranong Dc| +------------+----------+--------+-----------------+ 5 rows selected (0.411 seconds)
本文介紹了Hive的分桶和取樣,分桶很好的彌補了分割槽的一些不足。同時分桶之後,可以幫助我們更好的實現取樣。需要注意的是不論是分割槽還是分桶,都是存在計算的,所以分割槽或者分桶之後,資料匯入會比不分割槽不分桶慢,但換來的是後面查詢會更快速。
References
- https://cwiki.apache.org/confluence/display/Hive/LanguageManual
- https://hadooptutorial.info/bucketing-in-hive/
Hive除了有分割槽(Partition) ,還有分桶(Bucket) ,上一篇文章《Hive的分割槽》中介紹了分割槽,本文接著介紹分桶,以及和分桶經常一起使用的取樣(Sampling) 。
其實不管是分割槽還是分桶都是為了更好的管理資料。分割槽將表的資料分到不同的目錄儲存,從而在查詢的時候可以通過where條件過濾一部分資料,減小查詢的資料量從而提高效能。但分割槽的這種機制往往在資料符合以下條件時才會表現的比較好:
- 分割槽數目為有限個 :一般也不能太大,不然太多的檔案和目錄對於HDFS的NameNode會造成比較大的記憶體壓力。
- 各個分割槽的資料量比較均衡 :這個好理解,如果90%的資料跑到一個分割槽去,那分割槽的意義就不是很大了。
然而,並非所有場景都是符合上面兩條的。比如比較常見的按照國家等地理位置去分割槽的時候,可能幾個大國的資料就佔了總資料量的百分之七八十,而剩下的所有國家只佔了百分之二三十,也就是不符合上面的第二條。這個時候分割槽的好處就大打折扣了。為了克服這個問題,Hive增加了分桶的機制。我們先介紹一些分桶相關的基礎概念,然後通過例子再做說明。
1. 分桶
1.1 基礎理論
分桶的概念其實比較簡單:指定桶的個數,選中表中的若干個列,通過對資料中這些列的值做雜湊決定資料應該儲存到哪個桶裡面去,公式表示為:bucket num = hash_function(bucketing_column) mod num_buckets ,不同的資料型別雜湊函式不同。關於分桶還有以下一些關鍵點:
CLUSTERED BY
分桶也可以帶來如下一些好處:
- 分桶表比不分桶表的取樣(samping)效率高 。Hive提供了取樣機制,用於我們從大量資料中提取一部分來做測試或除錯,而分桶可以幫助我們更好的取樣(見後面的例子)。
- Map過程中的join會更快 。主要有兩方面原因:1. 分桶後,檔案大小均衡。2. join的時候左表可以計算對應的右表符合條件的行在哪個桶裡面,直接去獲取資料即可。
- 和分割槽一樣,分桶後查詢資料會更快 。
- 我們在桶內做排序,這樣可以讓Map裡面的join操作更快速。
下面我們看一個例子。
1.2 例子展示
場景需求: 還是使用上篇文章裡面的使用者資料UserRecords.txt ,但這次只使用國家作為分割槽列,州做雜湊列,也就是分桶列,並且桶內按照城市名排序。
和分割槽表一樣,分桶表也不能使用LOAD DATA
的方式匯入資料,所以我們還是像前文一樣,先把資料導到一張臨時表temp_user
裡面,這個步驟省略。如果不清楚的,請看上一篇文章(Hive的分割槽)。然後我們建立分桶表:
# 語法 CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name [(col_name data_type [COMMENT col_comment], ... [constraint_specification])] [COMMENT table_comment] [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS] # 建立bucketed_user表 CREATE TABLE bucketed_user ( firstnameVARCHAR(64), lastnameVARCHAR(64), addressSTRING, cityVARCHAR(64), stateVARCHAR(64), postSTRING, phone1VARCHAR(64), phone2STRING, emailSTRING, webSTRING ) COMMENT 'A bucketed sorted user table' PARTITIONED BY(country VARCHAR(64)) CLUSTERED BY(state) SORTED BY(city) INTO 32 BUCKETS;
我們使用可以看到,不像分割槽列是一個虛擬列,分桶列是表中的欄位。在插入欄位之前,我們先介紹兩個和分桶相關的配置項:
- hive.enforce.bucketing :如果建表時設定了分桶,插入資料的時候是否強制分桶。Hive 2.x版本之前預設為false,之後去掉了該配置,並將預設情況改為true。見HIVE-12331 .
- hive.enforce.sorting :如果建表時設定了分桶,插入資料的時候是排序,Hive 2.x版本之前預設為false,之後去掉了該配置,並將預設情況改為true。見HIVE-12331 .
在Hive 2.x之前,在定義表結構時通過CLUSTERED BY
指定分桶資訊,然後在插入資料的時候如果設定hive.enforce.bucketing
為true,則會按照表定義裡面的桶個數進行自動分桶(dynamic bucket);如果不設定為true,使用者也可以指定如何分桶(主要是指定reduce的個數
set mapred.reduce.tasks=xx
)。這樣導致的問題就是表結構裡面雖然定義了分桶資訊,但實際插入資料的時候可能並沒有分桶或者分桶方式與表結構裡面定義的不一致。這樣後續操作的時候有了很多不確定性,容易產生各種問題,所以在2.x版本之後,去掉了該項配置,直接將預設情況設定為了true。如果你用的Hive是2.x之前的版本,使用分桶的時候記得將這兩個選項置為true。另外需要說明的是桶的個數決定了reduce的個數。
下面我們來插入資料:
# 因為我們使用了動態分割槽,所以和前文一樣,設定動態分割槽相關的引數,引數含義見《Hive的分割槽》一文 set hive.exec.dynamic.partition=true; set hive.exec.dynamic.partition.mode=nonstrict; set hive.exec.max.dynamic.partitions.pernode=1000; # 覆蓋式插入資料 INSERT OVERWRITE TABLE bucketed_user PARTITION(country) SELECT firstname, lastname, address, city, state, post, phone1, phone2, email, web, country FROM temp_user;
執行插入語句的時候,大家可以看列印的日誌,一共有32個reducers,和桶的個數一致。插入完成之後我們來看HDFS上面的資料:
➜~ hadoop fs -ls -R /user/hive/warehouse/bucketed_user drwxrwxr-x- allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000000_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000001_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000002_0 -rwxrwxr-x1 allan supergroup806 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000003_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000004_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000005_0 -rwxrwxr-x1 allan supergroup17140 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000006_0 -rwxrwxr-x1 allan supergroup950 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000007_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000008_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000009_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000010_0 -rwxrwxr-x1 allan supergroup11314 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000011_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000012_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000013_0 -rwxrwxr-x1 allan supergroup4427 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000014_0 -rwxrwxr-x1 allan supergroup6132 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000015_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000016_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000017_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000018_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000019_0 -rwxrwxr-x1 allan supergroup12405 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000020_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000021_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000022_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000023_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000024_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000025_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000026_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000027_0 -rwxrwxr-x1 allan supergroup15262 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000028_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000029_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000030_0 -rwxrwxr-x1 allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=AU/000031_0 drwxrwxr-x- allan supergroup0 2018-09-23 17:40 /user/hive/warehouse/bucketed_user/country=CA -rwxrwxr-x1 allan supergroup ……省略後續輸出……
可以看到每個分割槽內的資料被分成了32份,因為資料量太小,所以很多桶內是空的。如果資料量大的話,各個桶的資料量會比較均衡。
2. 取樣
從大量資料中取樣得到少量資料進行測試或者除錯是資料分析中非常常見的操作,拋開業務在資料庫我們可以使用LIMIT
語句實現該功能,但Hive提供了一個TABLESAMPLE
語法用來更好的實現取樣,TABLESAMPLE
語句可以放到任何FROM語句中。目前支援兩種取樣方式:基於表的取樣Sampling Bucketized Table
和基於塊的取樣Block Sampling
。
2.1 Sampling Bucketized Table
基於分桶表的取樣的語法格式如下:
table_sample: TABLESAMPLE (BUCKET x OUT OF y [ON colname] [table_alias])
- colname 表示基於表中哪個欄位進行取樣,這個欄位可以是分桶列中的某個欄位,也可以不是;還可以使用rand() 表示基於整行而不是單獨某個欄位進行取樣。
-
x out of y:表示要取哪些桶的資料。假設表有32個桶:
- 3 out of 32 表示總共取32/32=1 個桶的資料,取的是第3個桶的資料;
- 3 out of 16 表示總共取32/16=2 個桶的資料,分別取第3個、第19(1+16)個桶的資料;
- 3 out of 64 表示總共取32/64=0.5 個桶的資料,取第3個桶一半的資料。
- table_alias 給表起的別名,類似MySQL的AS。
一些例子:
# 基於state欄位取樣 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(BUCKET 3 OUT OF 32 ON state); +-------------+----------+--------------+---------------------------------+ |firstname| country|state|city| +-------------+----------+--------------+---------------------------------+ | Marleen| CA| BC| Abbotsford| | Carole| CA| BC| Abbotsford| | Lasandra| CA| BC| Abbotsford| | Yvette| CA| AB| Big Valley| | Annamae| CA| BC| Burnaby| | Adela| CA| BC| Burnaby| ……省略…… | Shelia| UK| Cumbria| Silloth-on-Solway| | Mauricio| UK| Cumbria| Walney North Ward| | Quentin| US| MN| Burnsville| | Cyndy| US| MN| Burnsville| | Novella| US| HI| Hilo| | Brandon| US| HI| Honolulu| | Angella| US| HI| Honolulu| | Fatima| US| MN| Hopkins| | Skye| US| MN| Minneapolis| | Rodolfo| US| MN| Northfield| | Rolande| US| HI| Pearl City| | Chantell| US| MN| Saint Paul| | Matthew| US| MN| Shakopee| +-------------+----------+--------------+---------------------------------+ 131 rows selected (1.06 seconds) # 使用rand()取樣 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(BUCKET 3 OUT OF 32 ON rand()); +-------------+----------+---------------------+---------------------------------+ |firstname| country|state|city| +-------------+----------+---------------------+---------------------------------+ | Nenita| AU| NS| Botany| | Aide| AU| NS| Rhodes| | Hester| AU| NS| The Risk| | Annita| AU| NT| Karama| | Mariko| AU| WA| Hamel| | Emelda| AU| WA| Nedlands| | Leatha| AU| WA| Two Rocks| | Kenny| AU| TA| Nicholls Rivulet| | Eveline| AU| VI| Camberwell West| ……省略…… | Martina| US| FL| Orlando| | Billye| US| MS| Pearl| | Timothy| US| NY| Staten Island| | Pamella| US| CO| Denver| | Minna| US| PA| Kulpsville| | Fabiola| US| PA| York| | Junita| US| NJ| Cedar Grove| | Helaine| US| NJ| Jersey City| | Heike| US| NJ| Little Falls| | Eladia| US| NJ| Ramsey| | Felicidad| US| NJ| Riverton| +-------------+----------+---------------------+---------------------------------+ 58 rows selected (0.595 seconds)
需要注意的是這種取樣方式並不要求表一定要是分桶的 ,如果沒有分桶或者分桶了但取樣的欄位不在分桶欄位裡面,那也是可以正常取樣的,只不過取樣時會掃描全表資料,不是很高效而已。所以大多數情況這種取樣方式都是和分桶一起使用的,取樣的欄位就是分桶的欄位,這樣取樣時只掃描對應的桶就行,可以大大提高效率。
2.2 Block Sampling
基於塊的取樣是後來新增的一項功能(從Hive 0.8版本開始,見HIVE-2121 ),這裡的塊指的是HDFS的Block。目前有三種方式,基本語法為:
# 基於百分比 block_sample: TABLESAMPLE (n PERCENT) # 基於大小 block_sample: TABLESAMPLE (ByteLengthLiteral) ByteLengthLiteral : (Digit)+ ('b' | 'B' | 'k' | 'K' | 'm' | 'M' | 'g' | 'G') # 基於行數 block_sample: TABLESAMPLE (n ROWS)
基於百分比取樣和基於大小的取樣實質是一樣的(見HIVE-3401 ),這兩種方式目前不支援一些壓縮的格式。如果取樣失敗了,就會返回整個表或者分割槽的資料。需要注意的是,因為是基於塊取樣的,所以最小的取樣單位就是HDFS的一個block,也就是說返回的資料可能會比實際的資料多。比如%1的資料是100MB,但HDFS的一個block是256MB,那取樣得到的資料將是256MB。一個示例:
# 取樣0.1%的資料 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(0.1 PERCENT) sampled_bucketed_user; +------------+----------+-------------+---------+ | firstname| country|state|city| +------------+----------+-------------+---------+ | Soledad| AU| AC| Barton| | Darell| CA| ON| Ajax| | Allene| UK| Derbyshire| Barlow| | Devorah| US| NM| Clovis| +------------+----------+-------------+---------+ 4 rows selected (0.467 seconds) # 取樣100MB資料 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(100B) sampled_bucketed_user; +------------+----------+------------------+--------------------+ | firstname| country|state|city| +------------+----------+------------------+--------------------+ | Santos| AU| NS| Allworth| | Avery| CA| NS| Amherst| | Lewis| UK| South Yorkshire| Central Ward| | Weldon| US| IL| Arlington Heights| +------------+----------+------------------+--------------------+ 4 rows selected (0.187 seconds)
如果我們想保證每次取樣的資料一樣,可以設定種子:
set hive.sample.seednumber=<INTEGER>;
預設值是0,比如我們改為100,再採一次樣:
0: jdbc:hive2://localhost:10000> set hive.sample.seednumber=100; No rows affected (0.006 seconds) 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(0.1 PERCENT) sampled_bucketed_user; +------------+----------+------------------+--------------------+ | firstname| country|state|city| +------------+----------+------------------+--------------------+ | Santos| AU| NS| Allworth| | Avery| CA| NS| Amherst| | Lewis| UK| South Yorkshire| Central Ward| | Weldon| US| IL| Arlington Heights| +------------+----------+------------------+--------------------+ 4 rows selected (0.26 seconds)
基於行數的取樣和前面兩種方式不太一樣:
- 沒有資料格式的限制;
- 採集的條數n會在每個分片(split)都執行一次,所以採集到的總條數和輸入的分片數也有關係。
# 從輸入的每個分片從採5條資料(這裡只有一個分片) 0: jdbc:hive2://localhost:10000> SELECT firstname, country, state, city FROM bucketed_user TABLESAMPLE(5 ROWS) sampled_bucketed_user; +------------+----------+--------+-----------------+ | firstname| country| state|city| +------------+----------+--------+-----------------+ | Soledad| AU| AC| Barton| | Annamae| AU| AC| Civic Square| | Katheryn| AU| AC| Fyshwick| | Roy| AU| AC| Red Hill| | Jamie| AU| AC| Tuggeranong Dc| +------------+----------+--------+-----------------+ 5 rows selected (0.411 seconds)
本文介紹了Hive的分桶和取樣,分桶很好的彌補了分割槽的一些不足。同時分桶之後,可以幫助我們更好的實現取樣。需要注意的是不論是分割槽還是分桶,都是存在計算的,所以分割槽或者分桶之後,資料匯入會比不分割槽不分桶慢,但換來的是後面查詢會更快速。