Hadoop教程

Hive表格逻辑上由存储的数据和描述表格中数据形式的相关元数据组成。数据一 般存放在HDFS中,但它也可以放在其他任何Hadoop文件系统中,包括本地文件 系统或S3。Hive把元数据存放在关系数据库中,而不是放在HDFS中。

在这一节中,我们将进一步了解如何创建表格、Hive提供的不同物理存储格式以 及如何导入这些不同格式数据。

多数据库/模式支持

很多关系数据库提供了多个“命名空间”(namespace)的支持。这样,用户和应 用就可以隔离到不同的数据库或模式中。在写作本书时,Hive的所有表在同一个默认的命名空间中。但是,Hive 0.6.0计划支持多数据库,并提供CREATE DATABASE dbname、USE dbname以及DROP DATABASE dbname这样的语句。

托管表和外部表

在Hive中创建表时,默认情况下Hive负责管理数据。这意味着Hive把数据移入 它的“仓库目录”(warehouse directory)。另一种选择是创建一个“外部表” (external table)。这会让Hive到仓库目录以外的位置访问数据。

这两种表的区别表现在LOAD和DROP命令的语义上。先来看托管表(managed table)。加载数据到托管表时,出^把数据移到仓库目录。例如:

CREATE TABLE managed_table (dummy STRING);LOAD DATA INPATH ,/user/tom)data.txt, INTO table managed_table;

把文件hdfs://mer/tom/data.txt移动到Hive的仓库目录中managed_table表的目 录,即hdfs://user/hive/warehouse/mcmaged_table

由于加栽操作就是文件系统中的文件移动,因此它的执行速度很快:但记住,即使是托管表,Hive也并不检査表目录中的文件是否符合为 表所声明的模式。如果有数据和模式不匹配,只有在査询时才会知 道。我们通常要通过査询为缺失字段返回的空值NULL才知道存在不匹 配。可以发出一个简单的SELECT语句来査询表中的若干行数据,从而 检査数据是否能够被正确解析。

如果随后要丢弃一个表,可使用:

DROP TABLE managed_table;

然后这个表(包括它的元数据和数据)会被一起删除。在此我们要重复强调,因为最 初的LOAD是一个移动操作,而DROP是一个删除操作,所以数据会彻底消失。这 就是Hive所谓的“托管数据”的含义。

外部表而言,这两个操作的结果就不一样了:由你来控制数据的创建和删除。外部数据的位置需要在创建表的时候指明:

CREATE EXTERNAL TABLE external_table (dummy STRING)LOCATION '/user/tom/external_table';LOAD DATA INPATH'/use/tom/data.txt, INTO TABLE external_table;

使用EXTERNAL关键字以后,Hive知道数据并不由自己管理,因此不会把数据移 到自己的仓库目录。事实上,在定义时,它甚至不会检査这一外部位置是否存 在。这是一个非常重要的特性,因为这意味着你可以把创建数据推迟到创建表之后 才进行。

丢弃外部表时,Hive不会碰数据,而只会删除元数据。那么,应该如何选择使用 哪种表呢?在多数情况下,这两种方式没有太大的区别(当然DROP语义除外),因 此这只是个人喜好问题。作为一个经验法则,如果所有处理都由Hive完成,应该 使用托管表。但如果要用Hive和其他工具来处理同一个数据集,应该使用外部 表。普遍的用法是把存放在HDFS(由其他进程创建)的初始数据集用作外部表使, 然后用Hive的变换功能把数据移到托管的Hive表。这一方法反之也成立——外部 表(未必在HDFS中)可以用于从Hive导出数据供其他应用程序使用。


分区和桶

Hive把表组织成“分区” (partition)。这是一种根据“分区列” (partition column, 如日期)的值对表进行粗略划分的机制。使用分区可以加快数据分片(slice)的查询 速度。

表或分区可以进一步分为“桶”(bucket)。它会为数据提供额外的结构以获得更高 效的查询处理。例如,通过根据用户ID来划分桶,我们可以在所有用户集合的随 机样本上快速计算基于用户的查询。

分区

以分区的常用情况为例。考虑日志文件,其中每条记录包含一个时间戳。如果我们 根据日期来对它进行分区,那么同一天的记录就会被存放在同一个分区中。这样做 的优点是:对于限制到某个或某些特定日期的查询,它们的处理可以变得非常髙 效。因为它们只需要扫描查询范围内分区中的文件。注意,使用分区并不会影响大 范围查询的执行:我们仍然可以查询跨多个分区的整个数据集

一个表可以以多个维度来进行分区。在根据日期对日志进行分区以外,我们可能还 要进一步根据国家对每个分区进行子分区(subpartition),以加速根据地理位置进行 的查询。

分区是在创建表的时候用PARTITIONED BY子句定义的。该子句需要定义列的 列表。例如,对前面提到的假想的日志文件,我们可能要把表记录定义为由时间戳 和日志行构成:

CREATE TABLE logs (ts BIGINT, line STRING)PARTITIONED BY (dt STRING, country STRING);

在我们把数据加载到分区表的时候,要显式指定分区值:

LOAD DATA LOCAL INPATH input/hive/partitions/file1'INTO TABLE logsPARTITION (dt='2001-01-01', country='GB');

在文件系统级别,分区只是表目录下嵌套的子目录。把更多文件加载到日志表以 后,目录结构可能像下面这样:

/user/hive/warehouse/logs/dt=2010-01-01/country=GB/filel                                                  /file2                                       /country=US/file3                         /dt=2010-01-02/country=GB/file4                                       /country=US/file5                                                  /file6

日志表有两个日期分区(2010-01-01和2010-01-02,分别对应于子目录 dt=2010-01-01和dt=2010-01-02)和两个国家分区(GB和US,分别对应于嵌套 子目录〔country=GB日和country=US)。数据文件则存放在底层目录中。

可以用SHOW PARTITIONS命令让Hive告诉我们表中有哪些分区:

hive> SHOW PARTITIONS logs;dt=2001-01-01/country=GBdt=2001-01-01/country=USdt=2001-01-02/country=GBdt=2001-01-02/country=US

记住,PARTITIONED BY子句中的列定义是表中正式的列,称为“分区列” (partition column)。但是,数据文件并不包含这些列的值,因为它们源于目录名。

可以在SELECT语句中以普通方式使用分区列。Hive会对输入进行修剪,从而只 扫描相关的分区。例如:

SELECT ts, dt,line  FROM logsWHERE country='GB';

将只扫描file1, file2和file4。还要注意,这个查询返回dt分区列的值。这 个值是Hive从目录名中读取的,因为它们在数据文件中并不存在。

把表(或分区)组织成桶(bucket)有两个理由。第一个理由是获得更高的査询处理效 率。桶为表加上了额外的结构。Hive在处理有些查询时能够利用这个结构。具体 而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用map端连接(map-side join)髙效地实现。

把表划分成桶的第二个理由是使“取样”(sampling)更高效。在处理大规模数据集 时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,会 带来很多方便。我们在本节的最后将看看如何高效地进行取样。

首先,我们来看如何告诉Hive一个表应该被划分成桶。我们使用CLUSTERED BY子句来指定划分桶所用的列和要划分的桶的个数:

CREATE TABLE bucketed_usens (id INT) name STRING)CLUSTERED BY (id) INTO 4 BUCKETS;

在这里,我们使用用户ID来确定如何划分桶(Hive使用对值进行哈希并将结果除 以桶的个数取余数。这样,任何一桶里都会有一个随机的用户集合。

对于map端连接的情况,两个表以相同方式划分桶。处理左边表内某个桶的 mapper知道右边表内相匹配的行在对应的桶内。因此,mapper只需要获取那个桶 (这只是右边表内存储数据的一小部分)即可进行连接。这一优化方法并不一定要求 两个表必须桶的个数相同,两个表的桶个数是倍数关系也可以。

桶中的数据可以根据一个或多个列另外进行排序。由于这样对每个桶的连接变成了 高效的合并排序(merge-sort),因此可以进一步提升map端连接的效率。以下语法 声明一个表使其使用排序桶:

CREATE TABLE bucketed_users (id INT, name STRING)CLUSTERED BY (id) SORTED BY (id ASC) INTO 4 BUCKETS;

我们如何保证表中的数据都划分成桶了呢?把在Hive外生成的数据加载到划分成 桶的表中,当然是可以的。其实让Hive来划分桶更容易。这一操作通常针对已有 的表。

有一个没有划分桶的用户表:

hive>	SELECT * FROM users;0	   Nat2 	   Joe3  	   Kay4	   Ann

要向分桶表中填充成员,需要将hive.enforce.bucketing属性设置为ture。这 样,Hive就知道用表定义中声明的数量来创建桶。然后使用INSERT命令即可:

INSERT OVERWRITE TABLE bucketed_users SELECT * FROM users;

物理上,每个桶就是表(或分区)目录里的一个文件。它的文件名并不重要,但是桶 n是按照字典序排列的第n个文件。事实上,桶对应于MapReduce的输出文件分 区:一个作业产生的桶(输出文件)和reduce任务个数相同。我们可以通过查看刚才 创建的bucketd_users表的布局来了解这一情况。运行如下命令:

hive> dfs -ls /user/hive/warehouse/bucketed_users;

将显示有4个新建的文件。文件名如下(文件名包含时间戳,由Hive产生,因此 每次运行都会改变):

attempt_201005221636_0016_r_000000_0attempt_201005221636_0016_r_000001_0attempt_201005221636_0016_r_000002_0attempt_201005221636_0016_r_000003_0

第一个桶里包括用户ID0和4,因为一个INT的哈希值就是这个整数本身,在这里 除以桶数(4)以后的余数:

hive> dfs -cat /user/hive/warehouse/bucketed_users/*0_0;0Nat4Ann

用TABLESAMPLE子句对表进行取样,我们可以获得相同的结果。这个子句会将 查询限定在表的一部分桶内,而不是使用整个表:

hive> SELECT * FROM bucketed_usersTABLESAMPLE(BUCKET 1 OUT OF 4 ON id);0 Nat4 Ann

桶的个数从1开始计数。因此,前面的查询从4个桶的第一个中获取所有的用户。 对于一个大规模的、均匀分布的数据集,这会返回表中约四分之一的数据行。我们 也可以用其他比例对若干个桶进行取样(因为取样并不是一个精确的操作,因此淳个 比例不一定要是桶数的整数倍)。例如,下面的查询返回一半的桶:

hive> SELECT * FROM bucketed_usersTABLESAMPLE(BUCKET 1 OUT OF 2 ON id);0   Nat

因为查询只需要读取和TABLESAMPLE子句匹配的桶,所以取样分桶表是非常高效的操作。如果使用rand()函数对没有划分成桶的表进行取样,即使只需要读取很小一部分样本,也要扫描整个输入数据集:

hive> SELECT * FROM users    >TABLESAMPLE(BUCKET 1 OUT OF 4 ON rand());2 Joe

导入数据

我们已经见过如何使用LOAD DATA操作,通过把文件复制或移到表的目录中, 从而把数据导入Hive的表(或分区)。也可以用INSERT语句把数据从一个Hive表 填充到另一个,或在新建表的时候使用CTAS结构。CTAS是CREATE TABLE...AS SELECT 的缩写。

INSERT OVERWRITE TABLE

下面是INSERT语句的一个示例:

INSERT OVERWRITE TABLE targetSELECT coll, col2  FROM source;

对于分区的表,可以使用PARTITION子句来指明数据要插入哪个分区:

INSERT OVERWRITE TABLE target PARTITION (dt="2010-01-01')SELECT col1,col2 FROM source;

OVERWRITE关键字在这两种情况下都是强制的。这意味着目标表(对于前面的第一 个例子)或2010-01-01分区(对于第二个例子)中的内容会被SELECT语句的结果 替换掉。在本书写作时,Hive并不支持用INSERT语句向已经填充了内容的非分区表或分区添加记录。如果要那样做,可以使用不带OVERWRITE关键字的LOAD DATA操作。

从Hive 0.6.0开始,可以在SELECT语句中通过使用分区值来动态指明分区:

INSERT OVERWRITE TABLE target PARTITION (dt)SELECT col1, COl2, dt FROM source;

这种方法称为“动态分区插入”(dynamic-partition insert)。这一特性默认是关闭 的,所以在使用前需要先把hive.exec.dynamic.partition设为true。

多表插入

在HiveQL中,可以把INSERT语句倒过来,把FROM子句放在最前面,查询的效 果是一样的:

FROM sourceINSERT OVERWRITE TABLE target SELECT col1, col2;

可以在同一个查询中使用多个INSERT子句,这样的语法会让查询的含义更清楚。 这种“多表插入”(multitable insert)方法比使用多个单独的INSERT语句效率更 髙,因为只需要扫描一遍源表就可以生成多个不相交的输出。 下面的例子根据气象数据集来计算多种不同的统计数据:

FROM records2INSERT OVERWRITE TABLE stations_by_year SELECT year, COUNT(DISTINCT station)GROUP BY yearINSERT OVERWRITE TABLE records_by_year SELECT year, C0UNT(1)GROUP BY yearINSERT OVERWRITE TABLE good_records_by_year SELECT year, C0UNT(1)WHERE temperature != 9999AND (quality = 0 OR quality = 1 OR quality = 4 OR quality = 5 OR quality = 9) GROUP BY year;

这里只有一个源表(records2),但有三个表用于存放针对这个源表的三个不同査 询所产生的结果。

CREATE TABLE...AS SELECT

把Hive査询的输出结果存放到一个新的表往往非常方便,这可能是因为输出结果 太多,不适宜于显示在控制台上或基于输出结果还有其他后续处理。

新表的列的定义是从SELECT子句所检索的列导出的。在下面的査询中,目标表 有两列,分别名为col1和col2 ,它们的数据类型和源表中对应的列相同:

CREATE TABLE target ASSELECT col1, col2 FROM source;

CTAS操作是原子的,因此如果SELECT查询由于某种原因失败,是不会创建新表的。


表的修改

由于Hive使用“读时模式”(schema on read),所以在创建表以后,它非常灵活地 支持对表定义的修改。但一般需要警惕,在很多情况下,要由你来确保修改的数据 体现了新的结构。

可以使用ALTER TABLE语句来重命名表:

ALTER TABLE source RENAME TO target;

在更新表的元数据以外,ALTER TABLE语句还把表目录移到新名称所对应的目录 下。在这个示例中,/user/hive/warehouse/source 被重命名为 /user/hive/ware- house/target。对于外部表,这个操作只更新元数据,而不会移动目录。

Hive允许修改列的定义,添加新的列,甚至用一组新的列替换表内已有的列。例如,考虑添加一个新列:

ALTER TABLE target ADD COLUMNS (col3 STRING);

新的列COl3添加在已有(非分区)列的后面。数据文件并没有被更新,因此原来的 查询会为C0l3的所有值返回空值null(iS,除非文件中原来就已经有额外的字 段)。因为Hive并不允许更新已有的记录,所以需要使用其他机制来更新底层的文 件。为此,更常用的做法是创建一个定义了新列的新表,然后使用SELECT语句 把数据填充进去。

修改一个表的元数据——如列名或数据类型(假设原来的数据类型可以用新的数据 类型来解释)——更为直观。


表的丢弃

DROP TABLE语句用于删除表的数据和元数据。如果是外部表,就只删除元数 据——外部表不会受到影响。

如果要删除表内的所有数据但要保留表的定义(如MySQL的delete或TRUNCATE),删除数据文件即可。例如:

hive> dfs -rmr /user/hive/warehouse/my_table;

Hive把缺少文件(或根本没有表对应的目录)的表认为是空表。另一种达到类似目的 的方法是使用LIKE关键字创建一个与第一个表模式相同的新表:

CREATE TABLE new_table LIKE existing_table;

关注微信获取最新动态