分享

hive开发指导(2)

本帖最后由 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 表序列化。
LEFTRIGHTFULL 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.val1a.val2a.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),列 cd 将会被忽略,只会读取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,如图所示:
5.PNG
相关的参数为:
  • 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 FileSequence File
  • 内存中的数据格式: Java Integer/String, Hadoop IntWritable/Text
  • 用户提供的 map/reduce 脚本:不管什么语言,利用 stdin/stdout 传输数据
  • 用户自定义函数: Substr, Trim, 1 – 1
  • 用户自定义聚合函数: Sum, Average…… n – 1
File Format
TextFile
SequenceFIle
RCFFile
Data type
Text Only
Text/Binary
Text/Binary
Internal Storage Order
Row-based
Row-based
Column-based
Compression
File Based
Block Based
Block Based
Splitable
YES
YES
YES
Splitable After Compression
No
YES
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 见下图:

6.PNG


其中,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

UDFUser-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 提供了较好的性能(避免了反射)。




UDAFUser-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 的时候可以禁止部分聚合功能
UDFUDAF MR 脚本的对比:


7.PNG


设定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外部声明将不起作用。









没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条