本帖最后由 pig2 于 2014-2-11 18:52 编辑
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_expression ( 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 针对不同的查询进行了优化,优化可以通过配置进行控制,本文将介绍部分优化的策略以及优化控制选项。
列裁剪(Column Pruning)
在读数据的时候,只读取查询中需要用到的列,而忽略其他列。例如,对于查询:
SELECT a,b FROM T WHERE e < 10;其中,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 JoinJoin 操作在 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 是一个很开放的系统,很多内容都支持用户定制,包括:
- 文件格式:Text File,Sequence File
- 内存中的数据格式: Java Integer/String, Hadoop IntWritable/Text
- 用户提供的 map/reduce 脚本:不管什么语言,利用 stdin/stdout 传输数据
- 用户自定义函数: Substr, Trim, 1 – 1
- 用户自定义聚合函数: Sum, Average…… n – 1
File Format | TextFile | SequenceFIle | | | Text Only | Text/Binary | | | Row-based | Row-based | | | File Based | Block Based | | | YES | YES | | Splitable After Compression | No | YES | | 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';SerDeSerDe 是 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<a.length; i++)
if (a != null) total += a;
return total;
} // the same for Double public Double evaluate(Double... a) }使用例子为:
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 脚本的对比:
设定hive参数 开发Hive应用时,不可避免地需要设定Hive的参数。设定Hive的参数可以调优HQL代码的执行效率,或帮助定位问题。然而实践 中经常遇到的一个问题是,为什么设定的参数没有起作用? 这通常是错误的设定方式导致的。 对于一般参数,有以下三种设定方式: 配置文件:Hive的配置文件包括 - 用户自定义配置文件:$HIVE_CONF_DIR/hive-site.xml
- 默认配置文件:$HIVE_CONF_DIR/hive-default.xml
用户自定义配置会覆盖默认配置。另外,Hive也会读入Hadoop的配置,因为Hive是作为Hadoop的客户端启动的,Hadoop的配置文 件包括 - $HADOOP_CONF_DIR/hive-site.xml
- $HADOOP_CONF_DIR/hive-default.xml
Hive的配置会覆盖Hadoop的配置。
配置文件的设定对本机启动的所有Hive进程都有效。
命令行参数:
启动Hive(客户端或Server方式)时,可以在命令行添加-hiveconf param=value来设定参数,例如: bin/hive -hiveconf hive.root.logger=INFO,console 这一设定对本次启动的Session(对于Server方式启动,则是所有请求的Sessions)有效。
参数声明:
可以在HQL中使用SET关键字设定参数,例如: set mapred.reduce.tasks=100; 这一设定的作用域也是Session级的。
上述三种设定方式的优先级依次递增。即参数声明覆盖命令行参数,命令行参数覆盖配置文件设定。注意某些系统级的参数,例如log4j相关的设定,必须用前 两种方式设定,因为那些参数的读取在Session建立以前已经完成了。 另外,SerDe参数必须写在DDL(建表)语句中。例如:
- create table if not exists t_dummy(
- dummy string
- )
- ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
- WITH SERDEPROPERTIES (
- 'field.delim'='\t',
- 'escape.delim'='\\',
- 'serialization.null.format'=' '
- ) STORED AS TEXTFILE;
类似serialization.null.format这样的参数,必须和某个表或分区关联。在DDL外部声明将不起作用。
|