前言

有使用过Hive的人都知道,有一个叫做分区的东西。Hive表实际上是数据存储在HDFS上的抽象表,一个分区名对应逻辑存储时的一个子目录名,从形式上看可以理解为一个文件夹,一个表文件夹下的子文件夹。
有一点点绕,不知道你们听懂了没。

当然,分区并不是一个必要的概念,你可以建一张不带分区的表。大多数时候我们都建议建立分区表,用法为在建表语句后带关键字partitioned by (type string)声明。

那分区有什么好处呢,在Hive查询操作中,一般会扫描整个表内容,会消耗很多资源进行不必要的操作,如果限定分区来进行操作可以将表内容的范围提前缩小来提升效率。
在我实际应用中有两种分区的需求,一种是增量分区,另一种是全量分区。
差别就是每个分区中存储的是增量数据还是全量数据,一般来说数据量不大,数据内容更新频繁地建议使用全量分区;数据量极大,只会新增数据条目,不更新老数据的情况下使用增量分区,比如log日志。

全量分区有一个好处是可以追溯历史数据,这一功能有点像数据拉链表。但原理完全不同,全量分区简单地存储了历史上每一天的存量数据,比较浪费存储空间,这一点拉链表做地更好。但谁让现在硬盘便宜呢,加钱加硬盘呗,简单粗暴的解决方案。

动态分区

在我们实际应用中,我们一般用每天的日期来做分区。比如'20180531'就是一个分区,这个分区存储了2018-05-31 24:00:00时业务数据库的全量数据。这也是我上面提到的全量分区的使用场景。然而有些时候,单单用每天的日期来做分区是不够的,甚至需要每个小时来做分区。比如分区名为'2018053120'的小时分区。这个时候怎么办呢,这就需要轮到我们的主角登场:动态分区。
常规插入单分区时语法是这样的:

INSERT OVERWRITE TABLE project.table_name PARTITION (pt=${bdp.system.bizdate})
SELECT select_statement
FROM from_statement;

${bdp.system.bizdate}是MaxCompute的系统参数,默认为昨日日期的字符串,形如yyyymmmdd

如果我们要插入小时的分区,尝试这样写:

INSERT OVERWRITE TABLE project.table_name PARTITION (pth=SUBSTR(${bdp.system.cyctime}, 1, 10))
SELECT select_statement
FROM from_statement;

${bdp.system.cyctime}是MaxCompute的另一个系统参数,默认为运行脚本时的运行时间字符串,形如yyyymmddhh24miss

看起来很完美,实际运行会报错,因为在PARTITION子句里是不允许写函数的。
怎么办呢,MaxCompute提供了一种叫做动态分区(DYNAMIC PARTITION)的语法:先在分区中指定一个分区列名,但不给出值,而是在select子句中的对应列来提供分区的值。

INSERT OVERWRITE TABLE project.table_name PARTITION (pth)
SELECT  select_statement,SUBSTR(${bdp.system.cyctime}, 1, 10) AS pth
FROM from_statement;

注意

  • select_statement字段中,后面的字段将提供目标表动态分区值。如目标表就一级动态分区,则select_statement最后一个字段值即为目标表的动态分区值;
  • 动态生成的分区值不允许为NULL,也不支持含有特殊字符和中文,否则会引发异常;
  • 如果目标表有多级分区,在运行Insert语句时允许指定部分分区为静态,但是静态分区必须是高级分区;
  • 动态分区中,select_statement字段和目标表动态分区的对应是按字段顺序决定的,而不是字段名和分区名的对应关系。

按照上述写法,在SQL运行之前,是不知道会产生哪些分区的,只有在select运行结束后,才能由pth字段产生的值确定会产生哪些分区,这也是叫做动态分区的原因。

小时分区的小小改进

其实要实现按小时分区,这种单分区不是最好的选择。我们可以使用双分区来让数据存储更易维护。

INSERT OVERWRITE TABLE project.table_name PARTITION (pt=${bdp.system.bizdate},h)
SELECT  select_statement,SUBSTR(${bdp.system.cyctime}, 1, 10) AS h
FROM from_statement;

当然这种用法的前提是建表时得建立两个分区,pt和h。这样,我们就可以既按照天管理数据,也可以按照每一天里的小时管理数据,完美!