在上篇文章裡,我列舉了一個簡單的hive操作執行個體,建立了一張表test,并且向這張表加載了資料,這些操作和關系資料庫操作類似,我們常把hive和關系資料庫進行比較,也正是因為hive很多知識點和關系資料庫類似。
關系資料庫裡有表(table),分區,hive裡也有這些東西,這些東西在hive技術裡稱為hive的資料模型。今天本文介紹hive的資料類型,資料模型以及檔案存儲格式。這些知識大家可以類比關系資料庫的相關知識。
首先我要講講hive的資料類型。
Hive支援兩種資料類型,一類叫原子資料類型,一類叫複雜資料類型。
原子資料類型包括數值型、布爾型和字元串類型,具體如下表所示:
基本資料類型 | ||
類型 | 描述 | 示例 |
TINYINT | 1個位元組(8位)有符号整數 | 1 |
SMALLINT | 2位元組(16位)有符号整數 | 1 |
INT | 4位元組(32位)有符号整數 | 1 |
BIGINT | 8位元組(64位)有符号整數 | 1 |
FLOAT | 4位元組(32位)單精度浮點數 | 1.0 |
DOUBLE | 8位元組(64位)雙精度浮點數 | 1.0 |
BOOLEAN | true/false | true |
STRING | 字元串 | ‘xia’,”xia” |
由上表我們看到hive不支援日期類型,在hive裡日期都是用字元串來表示的,而常用的日期格式轉化操作則是通過自定義函數進行操作。
hive是用java開發的,hive裡的基本資料類型和java的基本資料類型也是一一對應的,除了string類型。有符号的整數類型:TINYINT、SMALLINT、INT和BIGINT分别等價于java的byte、short、int和long原子類型,它們分别為1位元組、2位元組、4位元組和8位元組有符号整數。Hive的浮點資料類型FLOAT和DOUBLE,對應于java的基本類型float和double類型。而hive的BOOLEAN類型相當于java的基本資料類型boolean。
對于hive的String類型相當于資料庫的varchar類型,該類型是一個可變的字元串,不過它不能聲明其中最多能存儲多少個字元,理論上它可以存儲2GB的字元數。
Hive支援基本類型的轉換,低位元組的基本類型可以轉化為高位元組的類型,例如TINYINT、SMALLINT、INT可以轉化為FLOAT,而所有的整數類型、FLOAT以及STRING類型可以轉化為DOUBLE類型,這些轉化可以從java語言的類型轉化考慮,因為hive就是用java編寫的。當然也支援高位元組類型轉化為低位元組類型,這就需要使用hive的自定義函數CAST了。
複雜資料類型包括數組(ARRAY)、映射(MAP)和結構體(STRUCT),具體如下表所示:
複雜資料類型 | ||
類型 | 描述 | 示例 |
ARRAY | 一組有序字段。字段的類型必須相同 | Array(1,2) |
MAP | 一組無序的鍵/值對。鍵的類型必須是原子的,值可以是任何類型,同一個映射的鍵的類型必須相同,值得類型也必須相同 | Map(‘a’,1,’b’,2) |
STRUCT | 一組命名的字段。字段類型可以不同 | Struct(‘a’,1,1,0) |
下面我們看看hive使用複雜資料類型的執行個體,建表:
Create table complex(col1 ARRAY<INT>,
Col2 MAP<STRING,INT>,
Col3 STRUCT<a:STRING,b :INT,c:DOUBLE>);
查詢語句:
Select col1[0],col2[‘b’],col3.c from complex;
接下來我們來看看hive的資料模型,hive的資料模型包括:database、table、partition和bucket。下面我将一一論述這四種資料模型。
1.Database:相當于關系資料庫裡的命名空間(namespace),它的作用是将使用者和資料庫的應用隔離到不同的資料庫或模式中,該模型在hive 0.6.0之後的版本支援,hive提供了create database dbname、use dbname以及drop database dbname這樣的語句。
2.表(table):hive的表邏輯上由存儲的資料和描述表格中的資料形式的相關中繼資料組成。表存儲的資料存放在分布式檔案系統裡,例如HDFS,中繼資料存儲在關系資料庫裡,當我們建立一張hive的表,還沒有為表加載資料的時候,該表在分布式檔案系統,例如hdfs上就是一個檔案夾(檔案目錄)。Hive裡的表友兩種類型一種叫托管表,這種表的資料檔案存儲在hive的資料倉庫裡,一種叫外部表,這種表的資料檔案可以存放在hive資料倉庫外部的分布式檔案系統上,也可以放到hive資料倉庫裡(注意:hive的資料倉庫也就是hdfs上的一個目錄,這個目錄是hive資料檔案存儲的預設路徑,它可以在hive的配置檔案裡進行配置,最終也會存放到中繼資料庫裡)。
下面是建立托管表的執行個體語句:
Create table tuoguan_tbl (flied string);
Load data local inpath ‘home/hadoop/test.txt’ into table tuoguan_tbl;
外部表建立的執行個體:
Create external table external_tbl (flied string)
Location ‘/home/hadoop/external_table’;
Load data local inpath ‘home/hadoop/test.txt’ into table external_tbl;
大家看到了建立外部表時候table之前要加關鍵字external,同時還要用location指令指定檔案存儲的路徑,如果不使用locaction資料檔案也會放置到hive的資料倉庫裡。
這兩種表在使用的差別主drop指令上,drop是hive删除表的指令,托管表執行drop指令的時候,會删除中繼資料和存儲的資料,而外部表執行drop指令時候隻删除中繼資料庫裡的資料,而不會删除存儲的資料。另外我還要談談表的load指令,hive加載資料時候不會對中繼資料進行任何檢查,隻是簡單的移動檔案的位置,如果源檔案格式不正确,也隻有在做查詢操作時候才能發現,那個時候錯誤格式的字段會以NULL來顯示。
3.分區(partition):hive裡分區的概念是根據“分區列”的值對表的資料進行粗略劃分的機制,在hive存儲上就展現在表的主目錄(hive的表實際顯示就是一個檔案夾)下的一個子目錄,這個檔案夾的名字就是我們定義的分區列的名字,沒有實際操作經驗的人可能會認為分區列是表的某個字段,其實不是這樣,分區列不是表裡的某個字段,而是獨立的列,我們根據這個列存儲表的裡的資料檔案。使用分區是為了加快資料分區的查詢速度而設計的,我們在查詢某個具體分區列裡的資料時候沒必要進行全表掃描。下面我就舉一個分區使用的執行個體:
建立分區:
Create table logs(ts bigint,line string)
Partitioned by (dt string,country string);
加載資料:
Local data local inpath ‘/home/hadoop/par/file01.txt’ into table logs partition (dt=’2012-06-02’,country=’cn’);
在hive資料倉庫裡實際存儲的路徑如下所示:
/user/hive/warehouse/logs/dt=2013-06-02/country=cn/file1.txt
/user/hive/warehouse/logs/dt=2013-06-02/country=cn/file2.txt
/user/hive/warehouse/logs/dt=2013-06-02/country=us/file3.txt
/user/hive/warehouse/logs/dt=2013-06-02/country=us/file4.txt
我們看到在表logs的目錄下有了兩層子目錄dt=2013-06-02和country=cn
查詢操作:
Select ts,dt,line from logs where country=’cn’,
這個時候我們的查詢操作隻會掃描file1.txt和file2.txt檔案。
4.桶(bucket):上面的table和partition都是目錄級别的拆分資料,bucket則是對資料源資料檔案本身來拆分資料。使用桶的表會将源資料檔案按一定規律拆分成多個檔案,要使用bucket,我們首先要打開hive對桶的控制,指令如下:
set hive.enforce.bucketing = true
下面這段文字是我引用部落格裡風生水起的博文:
示例:
建臨時表student_tmp,并導入資料:
hive> desc student_tmp;
OK
id int
age int
name string
stat_date string
Time taken: 0.106 seconds
hive> select * from student_tmp;
OK
1 20 zxm 20120801
2 21 ljz 20120801
3 19 cds 20120801
4 18 mac 20120801
5 22 android 20120801
6 23 symbian 20120801
7 25 wp 20120801
Time taken: 0.123 seconds
建student表:
hive>create table student(id INT, age INT, name STRING)
>partitioned by(stat_date STRING)
>clustered by(id) sorted by(age) into 2 bucket
>row format delimited fields terminated by ',';
設定環境變量:
>set hive.enforce.bucketing = true;
插入資料:
>from student_tmp
>insert overwrite table student partition(stat_date="20120802")
>select id,age,name where stat_date="20120801" sort by age;
檢視檔案目錄:
$ hadoop fs -ls /user/hive/warehouse/studentstat_date=20120802/
Found 2 items
-rw-r--r-- 1 work supergroup 31 2012-07-31 19:52 /user/hive/warehouse/student/stat_date=20120802/000000_0
-rw-r--r-- 1 work supergroup 39 2012-07-31 19:52 /user/hive/warehouse/student/stat_date=20120802/000001_0
實體上,每個桶就是表(或分區)目錄裡的一個檔案,桶檔案是按指定字段值進行hash,然後除以桶的個數例如上面例子2,最後去結果餘數,因為整數的hash值就是整數本身,上面例子裡,字段hash後的值還是字段本身,是以2的餘數隻有兩個0和1,是以我們看到産生檔案的字尾是*0_0和*1_0,檔案裡存儲對應計算出來的中繼資料。
Hive的桶,我個人認為沒有特别的場景或者是特别的查詢,我們可以沒有必要使用,也就是不用開啟hive的桶的配置。因為桶運用的場景有限,一個是做map連接配接的運算,我在後面的文章裡會講到,一個就是取樣操作了,下面還是引用風生水起博文裡的例子:
檢視sampling資料:
hive> select * from student tablesample(bucket 1 out of 2 on id);
Total MapReduce jobs = 1
Launching Job 1 out of 1
.......
OK
4 18 mac 20120802
2 21 ljz 20120802
6 23 symbian 20120802
Time taken: 20.608 seconds
tablesample是抽樣語句,文法:TABLESAMPLE(BUCKET x OUT OF y)
y必須是table總bucket數的倍數或者因子。hive根據y的大小,決定抽樣的比例。例如,table總共分了64份,當y=32時,抽取 (64/32=)2個bucket的資料,當y=128時,抽取(64/128=)1/2個bucket的資料。x表示從哪個bucket開始抽取。例 如,table總bucket數為32,tablesample(bucket 3 out of 16),表示總共抽取(32/16=)2個bucket的資料,分别為第3個bucket和第(3+16=)19個bucket的資料。