hive入門

Hive 是什麼

在接觸一個新的事物首先要回到的問題是:這是什麼?

這裏引用 Hive wiki 上的介紹:

Hive is a data warehouse infrastructure built on top of Hadoop. It provides tools to enable easy data ETL, a mechanism to put structures on the data, and the capability to querying and analysis of large data sets stored in Hadoop files. Hive defines a simple SQL-like query language, called QL, that enables users familiar with SQL to query the data. At the same time, this language also allows programmers who are familiar with the MapReduce fromwork to be able to plug in their custom mappers and reducers to perform more sophisticated analysis that may not be supported by the built-in capabilities of the language.

Hive 是建立在 Hadoop 上的數據倉庫基礎構架。它提供了一系列的工具,可以用來進行數據提取轉化加載(ETL),這是一種可以存儲、查詢和分析存儲在 Hadoop 中的大規模數據的機制。Hive 定義了簡單的類 SQL 查詢語言,稱爲 QL,它允許熟悉 SQL 的用戶查詢數據。同時,這個語言也允許熟悉 MapReduce 開發者的開發自定義的 mapper 和 reducer 來處理內建的 mapper 和 reducer 無法完成的複雜的分析工作。

Hive does not mandate read or written data be in the “Hive format”—there is no such thing. Hive works equally well on Thrift, control delimited, or your specialized data formats. Please see File Format and SerDe in Developer Guide for details.

Hive 沒有專門的數據格式。 Hive 可以很好的工作在 Thrift 之上,控制分隔符,也允許用戶指定數據格式。

Hive 資源

Hive 本身提供了較豐富的文檔,以下鏈接提供了 Hive 的一些基礎文檔:

  • FaceBook 鏡像(被牆):[[http://mirror.facebook.com/facebook/hive]]

  • Wiki 頁面:[[http://wiki.apache.org/hadoop/Hive]]

  • 入門指南:[[http://wiki.apache.org/hadoop/Hive/GettingStarted]]

  • 查詢語言指南:[[http://wiki.apache.org/hadoop/Hive/HiveQL]]

  • 演示文稿:[[http://wiki.apache.org/hadoop/Hive/Presentations]]

  • 藍圖:[[http://wiki.apache.org/hadoop/Hive/Roadmap]]

大多數有關 Hive 的使用和特性的問題可以從以上的鏈接中尋找到答案。當然,由於 Hive 本身在不斷的發展中,文檔的更新速度很多時候都趕不上 Hive 本身的更新速度,若希望瞭解 Hive 的最新動態或者遇到 Bug,可以加入 Hive 的郵件列表:
* User: [email protected]
* Developer: [email protected]

Hive 的下載配置安裝
請參考入門指南, 這裏給出最基本的提綱:

  • 安裝配置 Hadoop。

  • 安裝配置數據庫(mysql 等)。

  • 獲得 Hive 源碼或者可執行代碼。wget http://www.apache.org/dist/hadoop/hive/hive-0.5.0/hive-0.5.0-bin.tar.gz

  • tar xzf hive-0.5.0-bin.tar.gz

  • cd hive-0.5.0

  • 配置 Hive 如何訪問數據庫,如何訪問 Hadoop。

  • 運行 Hive。

當看到 Hive 提示符‘Hive>’的時候,恭喜,你可以開始你的 Hive 之旅了。


Hive 體系結構

Hive 的結構如圖所示,

主要分爲以下幾個部分:

  • 用戶接口,包括 CLI,Client,WUI。

  • 元數據存儲,通常是存儲在關係數據庫如 mysql, derby 中。

  • 解釋器、編譯器、優化器、執行器。

  • Hadoop:用 HDFS 進行存儲,利用 MapReduce 進行計算。

  1. 用戶接口主要有三個:CLI,Client 和 WUI。其中最常用的是 CLI,Cli 啓動的時候,會同時啓動一個 Hive 副本。Client 是 Hive 的客戶端,用戶連接至 Hive Server。在啓動 Client 模式的時候,需要指出 Hive Server 所在節點,並且在該節點啓動 Hive Server。 WUI 是通過瀏覽器訪問 Hive。

  2. Hive 將元數據存儲在數據庫中,如 mysql、derby。Hive 中的元數據包括表的名字,表的列和分區及其屬性,表的屬性(是否爲外部表等),表的數據所在目錄等。

  3. 解釋器、編譯器、優化器完成 HQL 查詢語句從詞法分析、語法分析、編譯、優化以及查詢計劃的生成。生成的查詢計劃存儲在 HDFS 中,並在隨後有 MapReduce 調用執行。

  4. Hive 的數據存儲在 HDFS 中,大部分的查詢由 MapReduce 完成(包含 * 的查詢,比如 select * from tbl 不會生成 MapRedcue 任務)。

Hive 元數據存儲

Hive 將元數據存儲在 RDBMS 中,有三種模式可以連接到數據庫:

  • Single User Mode: 此模式連接到一個 In-memory 的數據庫 Derby,一般用於 Unit Test。

  • Multi User Mode:通過網絡連接到一個數據庫中,是最經常使用到的模式。

  • Remote Server Mode:用於非 Java 客戶端訪問元數據庫,在服務器端啓動一個 MetaStoreServer,客戶端利用 Thrift 協議通過 MetaStoreServer 訪問元數據庫。

Hive 的數據存儲

首先,Hive 沒有專門的數據存儲格式,也沒有爲數據建立索引,用戶可以非常自由的組織 Hive 中的表,只需要在創建表的時候告訴 Hive 數據中的列分隔符和行分隔符,Hive 就可以解析數據。

其次,Hive 中所有的數據都存儲在 HDFS 中,Hive 中包含以下數據模型:Table,External Table,Partition,Bucket。

  1. Hive 中的 Table 和數據庫中的 Table 在概念上是類似的,每一個 Table 在 Hive 中都有一個相應的目錄存儲數據。例如,一個表 pvs,它在 HDFS 中的路徑爲:/wh/pvs,其中,wh 是在 hive-site.xml 中由 ${hive.metastore.warehouse.dir} 指定的數據倉庫的目錄,所有的 Table 數據(不包括 External Table)都保存在這個目錄中。

  2. Partition 對應於數據庫中的 Partition 列的密集索引,但是 Hive 中 Partition 的組織方式和數據庫中的很不相同。在 Hive 中,表中的一個 Partition 對應於表下的一個目錄,所有的 Partition 的數據都存儲在對應的目錄中。例如:pvs 表中包含 ds 和 city 兩個 Partition,則對應於 ds = 20090801, ctry = US 的 HDFS 子目錄爲:/wh/pvs/ds=20090801/ctry=US;對應於 ds = 20090801, ctry = CA 的 HDFS 子目錄爲;/wh/pvs/ds=20090801/ctry=CA

  3. Buckets 對指定列計算 hash,根據 hash 值切分數據,目的是爲了並行,每一個 Bucket 對應一個文件。將 user 列分散至 32 個 bucket,首先對 user 列的值計算 hash,對應 hash 值爲 0 的 HDFS 目錄爲:/wh/pvs/ds=20090801/ctry=US/part-00000;hash 值爲 20 的 HDFS 目錄爲:/wh/pvs/ds=20090801/ctry=US/part-00020

  4. External Table 指向已經在 HDFS 中存在的數據,可以創建 Partition。它和 Table 在元數據的組織上是相同的,而實際數據的存儲則有較大的差異。

  • Table 的創建過程和數據加載過程(這兩個過程可以在同一個語句中完成),在加載數據的過程中,實際數據會被移動到數據倉庫目錄中;之後對數據對訪問將會直接在數據倉庫目錄中完成。刪除表時,表中的數據和元數據將會被同時刪除。

  • External Table 只有一個過程,加載數據和創建表同時完成(CREATE EXTERNAL TABLE ……LOCATION),實際數據是存儲在 LOCATION 後面指定的 HDFS 路徑中,並不會移動到數據倉庫目錄中。當刪除一個 External Table 時,僅刪除

由於 Hive 採用了 SQL 的查詢語言 HQL,因此很容易將 Hive 理解爲數據庫。其實
從結構上來看,Hive 和數據庫除了擁有類似的查詢語言,再無類似之處。本文將
從多個方面來闡述 Hive 和數據庫的差異。數據庫可以用在 Online 的應用中,但是
Hive 是爲數據倉庫而設計的,清楚這一點,有助於從應用角度理解 Hive 的特性。


Hive 和數據庫的比較



查詢語言

HQL

SQL

數據存儲位置

HDFSRaw Device 或者 Local FS

數據格式

用戶定義系統決定

數據更新

支持不支持

索引

執行

MapRedcueExecutor

執行延遲

可擴展性

數據規模


  1. 查詢語言。由於 SQL 被廣泛的應用在數據倉庫中,因此,專門針對 Hive 的特性設計了類 SQL 的查詢語言 HQL。熟悉 SQL 開發的開發者可以很方便的使用 Hive 進行開發。

  2. 數據存儲位置。Hive 是建立在 Hadoop 之上的,所有 Hive 的數據都是存儲在 HDFS 中的。而數據庫則可以將數據保存在塊設備或者本地文件系統中。

  3. 數據格式。Hive 中沒有定義專門的數據格式,數據格式可以由用戶指定,用戶定義數據格式需要指定三個屬性:列分隔符(通常爲空格、”\\t”、”\\x001″)、行分隔符(”\\n”)以及讀取文件數據的方法(Hive 中默認有三個文件格式 TextFile,SequenceFile 以及 RCFile)。由於在加載數據的過程中,不需要從用戶數據格式到 Hive 定義的數據格式的轉換,因此,Hive 在加載的過程中不會對數據本身進行任何修改,而只是將數據內容複製或者移動到相應的 HDFS 目錄中。而在數據庫中,不同的數據庫有不同的存儲引擎,定義了自己的數據格式。所有數據都會按照一定的組織存儲,因此,數據庫加載數據的過程會比較耗時。

  4. 數據更新。由於 Hive 是針對數據倉庫應用設計的,而數據倉庫的內容是讀多寫少的。因此,Hive 中不支持對數據的改寫和添加,所有的數據都是在加載的時候中確定好的。而數據庫中的數據通常是需要經常進行修改的,因此可以使用 INSERT INTO ...  VALUES 添加數據,使用 UPDATE ... SET 修改數據。

  5. 索引。之前已經說過,Hive 在加載數據的過程中不會對數據進行任何處理,甚至不會對數據進行掃描,因此也沒有對數據中的某些 Key 建立索引。Hive 要訪問數據中滿足條件的特定值時,需要暴力掃描整個數據,因此訪問延遲較高。由於 MapReduce 的引入, Hive 可以並行訪問數據,因此即使沒有索引,對於大數據量的訪問,Hive 仍然可以體現出優勢。數據庫中,通常會針對一個或者幾個列建立索引,因此對於少量的特定條件的數據的訪問,數據庫可以有很高的效率,較低的延遲。由於數據的訪問延遲較高,決定了 Hive 不適合在線數據查詢。

  6. 執行。Hive 中大多數查詢的執行是通過 Hadoop 提供的 MapReduce 來實現的(類似 select * from tbl 的查詢不需要 MapReduce)。而數據庫通常有自己的執行引擎。

  7. 執行延遲。之前提到,Hive 在查詢數據的時候,由於沒有索引,需要掃描整個表,因此延遲較高。另外一個導致 Hive 執行延遲高的因素是 MapReduce 框架。由於 MapReduce 本身具有較高的延遲,因此在利用 MapReduce 執行 Hive 查詢時,也會有較高的延遲。相對的,數據庫的執行延遲較低。當然,這個低是有條件的,即數據規模較小,當數據規模大到超過數據庫的處理能力的時候,Hive 的並行計算顯然能體現出優勢。

  8. 可擴展性。由於 Hive 是建立在 Hadoop 之上的,因此 Hive 的可擴展性是和 Hadoop 的可擴展性是一致的(世界上最大的 Hadoop 集羣在 Yahoo!,2009年的規模在 4000 臺節點左右)。而數據庫由於 ACID 語義的嚴格限制,擴展行非常有限。目前最先進的並行數據庫 Oracle 在理論上的擴展能力也只有 100 臺左右。

  9. 數據規模。由於 Hive 建立在集羣上並可以利用 MapReduce 進行並行計算,因此可以支持很大規模的數據;對應的,數據庫可以支持的數據規模較小。

Hive QL

Hive 的官方文檔中對查詢語言有了很詳細的描述,請參考:http://wiki.apache.org/hadoop/Hive/LanguageManual ,本文的內容大部分翻譯自該頁面,期間加入了一些在使用過程中需要注意到的事項。

Create Table

CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
[(col_name data_type [COMMENT col_comment], ...)]
[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]
[ROW FORMAT row_format]
[STORED AS file_format]
[LOCATION hdfs_path]

CREATE TABLE 創建一個指定名字的表。如果相同名字的表已經存在,則拋出異常;用戶可以用 IF NOT EXIST 選項來忽略這個異常。

EXTERNAL 關鍵字可以讓用戶創建一個外部表,在建表的同時指定一個指向實際數據的路徑(LOCATION),Hive 創建內部表時,會將數據移動到數據倉庫指向的路徑;若創建外部表,僅記錄數據所在的路徑,不對數據的位置做任何改變。在刪除表的時候,內部表的元數據和數據會被一起刪除,而外部表只刪除元數據,不刪除數據。

LIKE 允許用戶複製現有的表結構,但是不復制數據。

用戶在建表的時候可以自定義 SerDe 或者使用自帶的 SerDe。如果沒有指定 ROW FORMAT 或者 ROW FORMAT DELIMITED,將會使用自帶的 SerDe。在建表的時候,用戶還需要爲表指定列,用戶在指定表的列的同時也會指定自定義的 SerDe,Hive 通過 SerDe 確定表的具體的列的數據。

如果文件數據是純文本,可以使用 STORED AS TEXTFILE。如果數據需要壓縮,使用 STORED AS SEQUENCE 。

有分區的表可以在創建的時候使用 PARTITIONED BY 語句。一個表可以擁有一個或者多個分區,每一個分區單獨存在一個目錄下。而且,表和分區都可以對某個列進行 CLUSTERED BY 操作,將若干個列放入一個桶(bucket)中。也可以利用SORT BY 對數據進行排序。這樣可以爲特定應用提高性能。

表名和列名不區分大小寫,SerDe 和屬性名區分大小寫。表和列的註釋是字符串。

Drop Table

刪除一個內部表的同時會同時刪除表的元數據和數據。刪除一個外部表,只刪除元數據而保留數據。

Alter Table

Alter table 語句允許用戶改變現有表的結構。用戶可以增加列/分區,改變serde,增加表和 serde 熟悉,表本身重命名。

Add Partitions

ALTER TABLE table_name ADD
partition_spec [ LOCATION 'location1' ]
partition_spec [ LOCATION 'location2' ] ...
partition_spec:
: PARTITION (partition_col = partition_col_value,
partition_col = partiton_col_value, ...)

用戶可以用 ALTER TABLE ADD PARTITION 來向一個表中增加分區。當分區名是字符串時加引號。

  ALTER TABLE page_view ADD
PARTITION (dt='2008-08-08', country='us')
location '/path/to/us/part080808'
PARTITION (dt='2008-08-09', country='us')
location '/path/to/us/part080809';

DROP PARTITION

ALTER TABLE table_name DROP
partition_spec, partition_spec,...

用戶可以用 ALTER TABLE DROP PARTITION 來刪除分區。分區的元數據和數據將被一併刪除。

ALTER TABLE page_view
DROP PARTITION (dt='2008-08-08', country='us');

RENAME TABLE

ALTER TABLE table_name RENAME TO new_table_name

這個命令可以讓用戶爲表更名。數據所在的位置和分區名並不改變。換而言之,老的表名並未“釋放”,對老表的更改會改變新表的數據。

Change Column Name/Type/Position/Comment

ALTER TABLE table_name CHANGE [COLUMN]
col_old_name col_new_name column_type
[COMMENT col_comment]
[FIRST|AFTER column_name]

這個命令可以允許用戶修改一個列的名稱、數據類型、註釋或者位置。

比如:

CREATE TABLE test_change (a int, b int, c int);

ALTER TABLE test_change CHANGE a a1 INT; 將 a 列的名字改爲 a1.

ALTER TABLE test_change CHANGE a a1 STRING AFTER b; 將 a 列的名字改爲 a1,a 列的數據類型改爲 string,並將它放置在列 b 之後。新的表結構爲: b int, a1 string, c int.

ALTER TABLE test_change CHANGE b b1 INT FIRST; 會將 b 列的名字修改爲 b1, 並將它放在第一列。新表的結構爲: b1 int, a string, c int.

注意:對列的改變只會修改 Hive 的元數據,而不會改變實際數據。用戶應該確定保證元數據定義和實際數據結構的一致性。

Add/Replace Columns

ALTER TABLE table_name ADD|REPLACE
COLUMNS (col_name data_type [COMMENT col_comment], ...)

ADD COLUMNS 允許用戶在當前列的末尾增加新的列,但是在分區列之前。

REPLACE COLUMNS 刪除以後的列,加入新的列。只有在使用 native 的 SerDE(DynamicSerDe or MetadataTypeColumnsetSerDe)的時候纔可以這麼做。

Alter Table Properties

ALTER TABLE table_name SET TBLPROPERTIES table_properties
table_properties:
: (property_name = property_value, property_name = property_value, ... )

用戶可以用這個命令向表中增加 metadata,目前 last_modified_user,last_modified_time 屬性都是由 Hive 自動管理的。用戶可以向列表中增加自己的屬性。可以使用 DESCRIBE EXTENDED TABLE 來獲得這些信息。

Add Serde Properties

ALTER TABLE table_name
SET SERDE serde_class_name
[WITH SERDEPROPERTIES serde_properties]
ALTER TABLE table_name
SET SERDEPROPERTIES serde_properties
serde_properties:
: (property_name = property_value,
property_name = property_value, ... )

這個命令允許用戶向 SerDe 對象增加用戶定義的元數據。Hive 爲了序列化和反序列化數據,將會初始化 SerDe 屬性,並將屬性傳給表的 SerDe。如此,用戶可以爲自定義的 SerDe 存儲屬性。

Alter Table File Format and Organization

ALTER TABLE table_name SET FILEFORMAT file_format
ALTER TABLE table_name CLUSTERED BY (col_name, col_name, ...)
[SORTED BY (col_name, ...)] INTO num_buckets BUCKETS

這個命令修改了表的物理存儲屬性。

Loading files into table

當數據被加載至表中時,不會對數據進行任何轉換。Load 操作只是將數據複製/移動至 Hive 表對應的位置。

Syntax:

LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE]
INTO TABLE tablename
[PARTITION (partcol1=val1, partcol2=val2 ...)]

Synopsis:

Load 操作只是單純的複製/移動操作,將數據文件移動到 Hive 表對應的位置。

  • filepath 可以是:

    • 相對路徑,例如:project/data1

    • 絕對路徑,例如: /user/hive/project/data1

    • 包含模式的完整 URI,例如:hdfs://namenode:9000/user/hive/project/data1


  • 加載的目標可以是一個表或者分區。如果表包含分區,必須指定每一個分區的分區名。

  • filepath 可以引用一個文件(這種情況下,Hive 會將文件移動到表所對應的目錄中)或者是一個目錄(在這種情況下,Hive 會將目錄中的所有文件移動至表所對應的目錄中)。

  • 如果指定了 LOCAL,那麼:

    • load 命令會去查找本地文件系統中的 filepath。如果發現是相對路徑,則路徑會被解釋爲相對於當前用戶的當前路徑。用戶也可以爲本地文件指定一個完整的 URI,比如:file:///user/hive/project/data1.

    • load 命令會將 filepath 中的文件複製到目標文件系統中。目標文件系統由表的位置屬性決定。被複制的數據文件移動到表的數據對應的位置。


  • 如果沒有指定 LOCAL 關鍵字,如果 filepath 指向的是一個完整的 URI,hive 會直接使用這個 URI。 否則:

    • 如果沒有指定 schema 或者 authority,Hive 會使用在 hadoop 配置文件中定義的 schema 和 authority,fs.default.name 指定了 Namenode 的 URI。

    • 如果路徑不是絕對的,Hive 相對於 /user/ 進行解釋。

    • Hive 會將 filepath 中指定的文件內容移動到 table (或者 partition)所指定的路徑中。


  • 如果使用了 OVERWRITE 關鍵字,則目標表(或者分區)中的內容(如果有)會被刪除,然後再將 filepath 指向的文件/目錄中的內容添加到表/分區中。

  • 如果目標表(分區)已經有一個文件,並且文件名和 filepath 中的文件名衝突,那麼現有的文件會被新文件所替代。

SELECT

Syntax

SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM table_reference
[WHERE where_condition]
[GROUP BY col_list]
[
CLUSTER BY col_list
| [DISTRIBUTE BY col_list]
[SORT BY col_list]
]
[LIMIT number]
  • 一個SELECT語句可以是一個union查詢或一個子查詢的一部分。

  • table_reference是查詢的輸入,可以是一個普通表、一個視圖、一個join或一個子查詢

  • 簡單查詢。例如,下面這一語句從t1表中查詢所有列的信息。

SELECT * FROM t1

WHERE Clause

where condition 是一個布爾表達式。例如,下面的查詢語句只返回銷售記錄大於 10,且歸屬地屬於美國的銷售代表。Hive 不支持在WHERE 子句中的 IN,EXIST 或子查詢。

SELECT * FROM sales WHERE amount > 10 AND region = "US"

ALL and DISTINCT Clauses

使用ALL和DISTINCT選項區分對重複記錄的處理。默認是ALL,表示查詢所有記錄。DISTINCT表示去掉重複的記錄。

hive> SELECT col1, col2 FROM t1
1 3
1 3
1 4
2 5
hive> SELECT DISTINCT col1, col2 FROM t1
1 3
1 4
2 5
hive> SELECT DISTINCT col1 FROM t1
1
2

基於Partition的查詢

一般 SELECT 查詢會掃描整個表(除非是爲了抽樣查詢)。但是如果一個表使用 PARTITIONED BY 子句建表,查詢就可以利用分區剪枝(input pruning)的特性,只掃描一個表中它關心的那一部分。Hive 當前的實現是,只有分區斷言出現在離 FROM 子句最近的那個WHERE 子句中,纔會啓用分區剪枝。例如,如果 page_views 表使用 date 列分區,以下語句只會讀取分區爲‘2008-03-01’的數據。

 SELECT page_views.*
FROM page_views
WHERE page_views.date >= '2008-03-01'
AND page_views.date

HAVING Clause

Hive 現在不支持 HAVING 子句。可以將 HAVING 子句轉化爲一個字查詢,例如:

SELECT col1 FROM t1 GROUP BY col1 HAVING SUM(col2) > 10

可以用以下查詢來表達:

SELECT col1 FROM (SELECT col1, SUM(col2) AS col2sum
FROM t1 GROUP BY col1) t2
WHERE t2.col2sum > 10

LIMIT Clause

Limit 可以限制查詢的記錄數。查詢的結果是隨機選擇的。下面的查詢語句從 t1 表中隨機查詢5條記錄:

SELECT * FROM t1 LIMIT 5

Top k 查詢。下面的查詢語句查詢銷售記錄最大的 5 個銷售代表。

SET mapred.reduce.tasks = 1
SELECT * FROM sales SORT BY amount DESC LIMIT 5

REGEX Column Specification

SELECT 語句可以使用正則表達式做列選擇,下面的語句查詢除了 ds 和 hr 之外的所有列:

SELECT `(ds|hr)?+.+` FROM sales

Join

Syntax

join_table:
table_reference JOIN table_factor [join_condition]
| table_reference {LEFT|RIGHT|FULL} [OUTER]
JOIN table_reference join_condition
| table_reference LEFT SEMI JOIN
table_reference join_condition
table_reference:
table_factor
| join_table
table_factor:
tbl_name [alias]
| table_subquery alias
| ( table_references )
join_condition:
ON equality_ AND  equality_expression )*
equality_expression:
expression = expression

Hive 只支持等值連接(equality joins)、外連接(outer joins)和(left semi joins???)。Hive 不支持所有非等值的連接,

因爲非等值連接非常難轉化到 map/reduce 任務。另外,Hive 支持多於 2 個表的連接。

寫 join 查詢時,需要注意幾個關鍵點:
1. 只支持等值join,例如:

  SELECT a.* FROM a JOIN b ON (a.id = b.id)
SELECT a.* FROM a JOIN b
ON (a.id = b.id AND a.department = b.department)

是正確的,然而:

  SELECT a.* FROM a JOIN b ON (a.id  b.id)

是錯誤的。

2. 可以 join 多於 2 個表,例如

  SELECT a.val, b.val, c.val FROM a JOIN b
ON (a.key = b.key1) JOIN c ON (c.key = b.key2)

如果join中多個表的 join key 是同一個,則 join 會被轉化爲單個 map/reduce 任務,例如:

  SELECT a.val, b.val, c.val FROM a JOIN b
ON (a.key = b.key1) JOIN c
ON (c.key = b.key1)

被轉化爲單個 map/reduce 任務,因爲 join 中只使用了 b.key1 作爲 join key。

SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1)
JOIN c ON (c.key = b.key2)

而這一 join 被轉化爲 2 個 map/reduce 任務。因爲 b.key1 用於第一次 join 條件,而 b.key2 用於第二次 join。

join 時,每次 map/reduce 任務的邏輯是這樣的:reducer 會緩存 join 序列中除了最後一個表的所有表的記錄,

再通過最後一個表將結果序列化到文件系統。這一實現有助於在 reduce 端減少內存的使用量。實踐中,應該把最大的那個表寫在最後

(否則會因爲緩存浪費大量內存)。例如:

 SELECT a.val, b.val, c.val FROM a
JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1)

所有表都使用同一個 join key(使用 1 次 map/reduce 任務計算)。Reduce 端會緩存 a 表和 b 表的記錄,然後每次取得一個 c 表的記錄就計算一次 join 結果,類似的還有:

  SELECT a.val, b.val, c.val FROM a
JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2)

這裏用了 2 次 map/reduce 任務。第一次緩存 a 表,用 b 表序列化;第二次緩存第一次 map/reduce 任務的結果,然後用 c 表序列化。

LEFT,RIGHT 和 FULL OUTER 關鍵字用於處理 join 中空記錄的情況,例如:

  SELECT a.val, b.val FROM a LEFT OUTER
JOIN b ON (a.key=b.key)

對應所有 a 表中的記錄都有一條記錄輸出。輸出的結果應該是 a.val, b.val,當 a.key=b.key 時,而當 b.key 中找不到等值的

a.key 記錄時也會輸出 a.val, NULL。“FROM a LEFT OUTER JOIN b”這句一定要寫在同一行——意思是 a 表在 b 表的左邊,所以

a 表中的所有記錄都被保留了;“a RIGHT OUTER JOIN b”會保留所有 b 表的記錄。OUTER JOIN 語義應該是遵循標準 SQL spec的。

Join 發生在 WHERE 子句之前。如果你想限制 join 的輸出,應該在 WHERE 子句中寫過濾條件——或是在 join 子句中寫。

這裏面一個容易混淆的問題是表分區的情況:

  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'

會 join a 表到 b 表(OUTER JOIN),列出 a.val 和 b.val 的記錄。WHERE 從句中可以使用其他列作爲過濾條件。

但是,如前所述,如果 b 表中找不到對應 a 表的記錄,b 表的所有列都會列出 NULL,包括 ds 列。也就是說,join

會過濾 b 表中不能找到匹配 a 表 join key 的所有記錄。這樣的話,LEFT OUTER 就使得查詢結果與 WHERE 子句無關了。

解決的辦法是在 OUTER JOIN 時使用以下語法:

  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')

這一查詢的結果是預先在 join 階段過濾過的,所以不會存在上述問題。這一邏輯也可以應用於 RIGHT 和 FULL 類型的 join 中。

Join 是不能交換位置的。無論是 LEFT 還是 RIGHT join,都是左連接的。

  SELECT a.val1, a.val2, b.val, c.val
FROM a
JOIN b ON (a.key = b.key)
LEFT OUTER JOIN c ON (a.key = c.key)

先 join a 表到 b 表,丟棄掉所有 join key 中不匹配的記錄,然後用這一中間結果和 c 表做 join。這一表述有一個不太明顯的問題,

就是當一個 key 在 a 表和 c 表都存在,但是 b 表中不存在的時候:整個記錄在第一次 join,即 a JOIN b 的時候都被丟掉了

(包括a.val1,a.val2和a.key),然後我們再和 c 表 join 的時候,如果 c.key 與 a.key 或 b.key 相等,就會得到這樣的結果:

NULL, NULL, NULL, c.val。

LEFT SEMI JOIN 是 IN/EXISTS 子查詢的一種更高效的實現。Hive 當前沒有實現 IN/EXISTS 子查詢,所以你可以用 LEFT SEMI JOIN

重寫你的子查詢語句。LEFT SEMI JOIN 的限制是, JOIN 子句中右邊的表只能在 ON 子句中設置過濾條件,在 WHERE 子句、

SELECT 子句或其他地方過濾都不行。

  SELECT a.key, a.value
FROM a
WHERE a.key in
(SELECT b.key
FROM B);

可以被重寫爲:

   SELECT a.key, a.val
FROM a LEFT SEMI JOIN b on (a.key = b.key

Hive 優化

Hive 針對不同的查詢進行了優化,優化可以通過配置進行控制,本文將介紹部分優化的策略以及優化控制選項。

列裁剪(Column Pruning)

在讀數據的時候,只讀取查詢中需要用到的列,而忽略其他列。例如,對於查詢:

SELECT a,b FROM T WHERE e

其中,T 包含 5 個列 (a,b,c,d,e),列 c,d 將會被忽略,只會讀取a, b, e 列

這個選項默認爲真: hive.optimize.cp = true

分區裁剪(Partition Pruning)

在查詢的過程中減少不必要的分區。例如,對於下列查詢:

SELECT * FROM (SELECT c1, COUNT(1)
FROM T GROUP BY c1) subq
WHERE subq.prtn = 100;
SELECT * FROM T1 JOIN
(SELECT * FROM T2) subq ON (T1.c1=subq.c2)
WHERE subq.prtn = 100;

會在子查詢中就考慮 subq.prtn = 100 條件,從而減少讀入的分區數目。

此選項默認爲真:hive.optimize.pruner=true

Join

在使用寫有 Join 操作的查詢語句時有一條原則:應該將條目少的表/子查詢放在 Join 操作符的左邊。原因是在 Join 操作的 Reduce

階段,位於 Join 操作符左邊的表的內容會被加載進內存,將條目少的表放在左邊,可以有效減少發生 OOM 錯誤的機率。

對於一條語句中有多個 Join 的情況,如果 Join 的條件相同,比如查詢:

INSERT OVERWRITE TABLE pv_users
SELECT pv.pageid, u.age FROM page_view p
JOIN user u ON (pv.userid = u.userid)
JOIN newuser x ON (u.userid = x.userid);
  • 如果 Join 的 key 相同,不管有多少個表,都會則會合併爲一個 Map-Reduce

  • 一個 Map-Reduce 任務,而不是 ‘n’ 個

  • 在做 OUTER JOIN 的時候也是一樣

如果 Join 的條件不相同,比如:

  INSERT OVERWRITE TABLE pv_users
SELECT pv.pageid, u.age FROM page_view p
JOIN user u ON (pv.userid = u.userid)
JOIN newuser x on (u.age = x.age);

Map-Reduce 的任務數目和 Join 操作的數目是對應的,上述查詢和以下查詢是等價的:

  INSERT OVERWRITE TABLE tmptable
SELECT * FROM page_view p JOIN user u
ON (pv.userid = u.userid);
INSERT OVERWRITE TABLE pv_users
SELECT x.pageid, x.age FROM tmptable x
JOIN newuser y ON (x.age = y.age);

Map Join

Join 操作在 Map 階段完成,不再需要Reduce,前提條件是需要的數據在 Map 的過程中可以訪問到。比如查詢:

  INSERT OVERWRITE TABLE pv_users
SELECT /*+ MAPJOIN(pv) */ pv.pageid, u.age
FROM page_view pv
JOIN user u ON (pv.userid = u.userid);

可以在 Map 階段完成 Join,如圖所示:

相關的參數爲:

  • hive.join.emit.interval = 1000 How many rows in the right-most join operand Hive should buffer before emitting the join result.

  • hive.mapjoin.size.key = 10000

  • hive.mapjoin.cache.numrows = 10000

Group By

  • Map 端部分聚合:

    • 並不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map 端進行部分聚合,最後在 Reduce 端得出最終結果。

    • 基於 Hash

    • 參數包括:

      • hive.map.aggr = true 是否在 Map 端進行聚合,默認爲 True

      • hive.groupby.mapaggr.checkinterval = 100000 在 Map 端進行聚合操作的條目數目



  • 有數據傾斜的時候進行負載均衡

    • hive.groupby.skewindata = false

    • 當選項設定爲 true,生成的查詢計劃會有兩個 MR Job。第一個 MR Job 中,Map 的輸出結果集合會隨機分佈到 Reduce 中,每個 Reduce 做部分聚合操作,並輸出結果,這樣處理的結果是相同的 Group By Key 有可能被分發到不同的 Reduce 中,從而達到負載均衡的目的;第二個 MR Job 再根據預處理的數據結果按照 Group By Key 分佈到 Reduce 中(這個過程可以保證相同的 Group By Key 被分佈到同一個 Reduce 中),最後完成最終的聚合操作。


合併小文件

文件數目過多,會給 HDFS 帶來壓力,並且會影響處理效率,可以通過合併 Map 和 Reduce 的結果文件來消除這樣的影響:

  • hive.merge.mapfiles = true 是否和並 Map 輸出文件,默認爲 True

  • hive.merge.mapredfiles = false 是否合併 Reduce 輸出文件,默認爲 False

  • hive.merge.size.per.task = 256*1000*1000 合併文件的大小

Hive 的擴展特性
  CREATE TABLE mylog ( user_id BIGINT, page_url STRING, unix_time INT)
STORED AS TEXTFILE;

當用戶的數據文件格式不能被當前 Hive 所識別的時候,可以自定義文件格式。可以參考 contrib/src/java/org/apache/hadoop/hive/contrib/fileformat/base64 中的例子。寫完自定義的格式後,在創建表的時候指定相應的文件格式就可以:

  CREATE TABLE base64_test(col1 STRING, col2 STRING)
STORED AS
INPUTFORMAT 'org.apache.hadoop.hive.contrib.
fileformat.base64.Base64TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.contrib.
fileformat.base64.Base64TextOutputFormat';

SerDe

SerDe 是 Serialize/Deserilize 的簡稱,目的是用於序列化和反序列化。序列化的格式包括:

  • 分隔符(tab、逗號、CTRL-A)

  • Thrift 協議

反序列化(內存內):

  • Java Integer/String/ArrayList/HashMap

  • Hadoop Writable 類

  • 用戶自定義類

目前存在的 Serde 見下圖:

其中,LazyObject 只有在訪問到列的時候才進行反序列化。 BinarySortable:保留了排序的二進制格式。

當存在以下情況時,可以考慮增加新的 SerDe:

  • 用戶的數據有特殊的序列化格式,當前的 Hive 不支持,而用戶又不想在將數據加載至 Hive 前轉換數據格式。

  • 用戶有更有效的序列化磁盤數據的方法。

用戶如果想爲 Text 數據增加自定義 Serde ,可以參照 contrib/src/java/org/apache/hadoop/hive/contrib/serde2/RegexSerDe.java 中的例子。RegexSerDe 利用用戶提供的正則表倒是來反序列化數據,例如:

  CREATE TABLE apache_log(
host STRING,
identity STRING,
user STRING,
time STRING,
request STRING,
status STRING,
size STRING,
referer STRING,
agent STRING)
ROW FORMAT
SERDE 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
WITH SERDEPROPERTIES
( "input.regex" = "([^ ]*) ([^ ]*) ([^ ]*) (-|\\\\[[^\\\\]]*\\\\])
([^ \\"]*|\\"[^\\"]*\\") (-|[0-9]*) (-|[0-9]*)(?: ([^ \\"]*|\\"[^\\"]*\\")
([^ \\"]*|\\"[^\\"]*\\"))?",
"output.format.string" = "%1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s";)
STORED AS TEXTFILE;

用戶如果想爲 Binary 數據增加自定義的 SerDE,可以參考例子:serde/src/java/org/apache/hadoop/hive/serde2/binarysortable,例如:

  CREATE TABLE mythrift_table
ROW FORMAT SERDE
'org.apache.hadoop.hive.contrib.serde2.thrift.ThriftSerDe'
WITH SERDEPROPERTIES (
"serialization.class" = "com.facebook.serde.tprofiles.full",
"serialization.format" = "com.facebook.thrift.protocol.TBinaryProtocol";);

Map/Reduce 腳本(Transform)

用戶可以自定義 Hive 使用的 Map/Reduce 腳本,比如:

  FROM (
SELECT TRANSFORM(user_id, page_url, unix_time)
USING 'page_url_to_id.py'
AS (user_id, page_id, unix_time)
FROM mylog
DISTRIBUTE BY user_id
SORT BY user_id, unix_time)
mylog2
SELECT TRANSFORM(user_id, page_id, unix_time)
USING 'my_python_session_cutter.py' AS (user_id, session_info);

Map/Reduce 腳本通過 stdin/stdout 進行數據的讀寫,調試信息輸出到 stderr。

UDF(User-Defined-Function)

用戶可以自定義函數對數據進行處理,例如:

  add jar build/ql/test/test-udfs.jar;
CREATE TEMPORARY FUNCTION testlength
AS 'org.apache.hadoop.hive.ql.udf.UDFTestLength';
SELECT testlength(src.value) FROM src;
DROP TEMPORARY FUNCTION testlength;

UDFTestLength.java 爲:

  package org.apache.hadoop.hive.ql.udf;
public class UDFTestLength extends UDF {
public Integer evaluate(String s) {
if (s == null) {
return null;
}
return s.length();
}
}

自定義函數可以重載:

  add jar build/contrib/hive_contrib.jar;
CREATE TEMPORARY FUNCTION example_add
AS 'org.apache.hadoop.hive.contrib.udf.example.UDFExampleAdd';
SELECT example_add(1, 2) FROM src;
SELECT example_add(1.1, 2.2) FROM src;

UDFExampleAdd.java:

  public class UDFExampleAdd extends UDF {
public Integer evaluate(Integer a, Integer b) {
if (a = null || b = null)
return null;
return a + b;
}
public Double evaluate(Double a, Double b) {
if (a = null || b = null)
return null;
return a + b;
}
}

%%

在使用 UDF 的時候,會自動進行類型轉換,這個 java 或者 C 中的類型轉換有些類似,比如:

  SELECT example_add(1, 2.1) FROM src;

的結果是 3.1,這是因爲 UDF 將類型爲 Int 的參數 “1″ 轉換爲 double。

類型的隱式轉換是通過 UDFResolver 來進行控制的,並且可以根據不同的 UDF 進行不同的控制。

UDF 還可以支持變長的參數,例如 UDFExampleAdd.java:

  public class UDFExampleAdd extends UDF {
public Integer evaluate(Integer... a) {
int total = 0;
for (int i=0; i

使用例子爲:

  SELECT example_add(1, 2) FROM src;
SELECT example_add(1, 2, 3) FROM src;
SELECT example_add(1, 2, 3, 4.1) FROM src;

綜上,UDF 具有以下特性:

  • 用 java 寫 UDF 很容易。

  • Hadoop 的 Writables/Text 具有較高性能。

  • UDF 可以被重載。

  • Hive 支持隱式類型轉換。

  • UDF 支持變長的參數。

  • genericUDF 提供了較好的性能(避免了反射)。

UDAF(User-Defined Aggregation Funcation)

例子:

  SELECT page_url, count(1), count(DISTINCT user_id) FROM mylog;

UDAFCount.java:

  public class UDAFCount extends UDAF {
public static class Evaluator implements UDAFEvaluator {
private int mCount;
public void init() {
mcount = 0;
}
public boolean iterate(Object o) {
if (o!=null)
mCount++;
return true;
}
public Integer terminatePartial() {
return mCount;
}
public boolean merge(Integer o) {
mCount += o;
return true;
}
public Integer terminate() {
return mCount;
}
}

UDAF 總結:

  • 編寫 UDAF 和 UDF 類似

  • UDAF 可以重載

  • UDAF 可以返回複雜類

  • 在使用 UDAF 的時候可以禁止部分聚合功能

UDF,UDAF 和 MR 腳本的對比:



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