Hive配置运行及表的操作
配置Hive
Hive的配置文件名为hive-site.xml,你可以在Hive安装目录下的conf目录下找到这个文件。如果你发现该目录下没有这个文件,你可以通过复制hive-default.xml.template来生成该文件。
当然,你也可以在进入hive时指定参数来明确指明配置文件所在目录。比如:
hive --config /home/user/hive-conf
你也可以在进入hive时,通过命令行指定特定的配置参数值,比如:
hive --config fs.defalut.name=localhost:9000
以上命令在进入hive时,设置文件系统为localhost。如果在配置文件中同样包含对该属性值的设置,则命令行的设置将覆盖配置文件中的设置。
你也可以在进入hive后,通过set命令进行设置,比如:
set fs.default.name=localhost:9000;
如以上所述,有很多方式都可以设置配置属性值。如果在多个地方对同一属性值进行了设置,则其优先级由高到低依次为:
l Hive set命令
l 命令行—config设置
l Hive-site.xml
l Hive-default.xml
l Hadoop-site.xml(或core-site.xml等)
l Hadoop-default.xml
注意:以上提到的hadoop-site.xml及haddop-default.xml文件为Hadoop的配置文件,默认在Hadoop安装目录下的etc/hadoop目录下。Pig通过Haddop相关的环境变量来获取这些配置文件中的关联信息。
日志
Hive默认的日志文件为/tmp/{user}/hive.log。当你运行hive过程中碰到问题需要分析时,查看这个日志文件是一个非常好的办法。Hive默认使用log4j来记录日志。
Hive服务
前面提到的Shell只是Hive提供的服务之一。你可以在命令行通过--service选项指定运行某一服务。Hive提供的服务包括:
1) Cli
命令行接口(command lineinterface),即Shell。通过以下命令进入shell:
hive --service cli
注意:cli为默认方式,即以上命令等同于不带任何参数的hive命令。
2) Hiveserver
通过提供Thrift接口服务来运行Hive服务,可供多种客户端通过Thrift来于Hive进行通讯。Thrift服务我们后续来讲。
3) Hwi
Hive的Web接口。
要使用hwi,需要先安装ant,并配置ant的环境变量,比如:
export ANT_LIB=/user/share/ant/lib
完成后,运行以下命令:
Hive –service hwi
然后通过localhost:9999/hwi访问。
注意:
访问hwi时,可能会碰到各种各样的问题,比如Unable to find a javac compiler等等。此时,请尝试以下操作:
l 找到jdk目录下的tools.jar,复制并拷贝到hive的lib目录下。
l 找到ant目录下的ant.jar和ant-launcher.jar文件,复制并拷贝到hive的lib目录下。
4) jar
与Hadoop Jar的使用方法类似。
5) metadata
默认情况下,metadata和hive运行在同一进程中。通过此方式,可以让metadata以单独的进程运行。
6) Hive客户端
有很多种方式可以通过客户端连接到Hive服务器。
l Thrift 客户端
l JDBC驱动
l ODBC驱动
我们在以后的实例中,逐个讲解。
Metastore
Metastore包含两个部分:服务和数据存储。默认情况下,metastore服务和hive服务运行在同一个jvm中,包含一个内嵌的本地Derby数据库实例。
使用内嵌的数据库存储metastore元数据是最简单的处理方式。然而这就意味着同一时间只有一个Derby数据库实例可以访问数据库磁盘文件,此时如果你启动第二个回话,Hive就会报错。
如果希望Hive支持多会话,我们就需要一个独立的数据库支持。这种方式我们称之为本地Metastore(Local metastore)。在这种工作方式下,Metastore服务仍然和Hiveservice工作在同一个进程中,但是连接到一个独立的数据库服务进程(甚至可以是一个远程的数据库服务进程)。一般情况下支持JDBC的数据库都可以配置作为该数据库服务支持程序。
MySQL是一个非常合适的选择,用来支持Metastore数据存储。此种情况下,我们需要设置:
javax.jdo.option.ConnectionURL = jdbc:mysql://host/dbname?createDatabaseIfNotExist=true
javax.jdo.option.ConnectionDriverName = com.mysql.jdbc.Driver
javax.jdo.option.ConnectionUserName = username
javax.jdo.option.ConnectionPassword = password
注意:请确保对应的JDBC驱动的jar文件存在于hive的classpath,比如hive的lib目录下。
另外一种方式我们称之为远程metastore。在这种配置下,Metastore服务运行在自己独立的进程中,其他进程通过Thrift和metastore服务进行通讯。这种方式的好处之一就是客户端不需要提供JDBC登录信息等敏感数据。
注意:
Local或者Remote方式不是以数据库是否在本地来区分的,也就是说remote方式下database数据库仍然可以和Hive运行在同一个机器上。
请参照Hive官方文档了解更多关于以上三种方式的详细信息。
和传统数据库的比较
Hive某些方面和传统数据库是基本一致的,比如SQL语法。然而Hive的基于Mapreduce和hdfs的特性,决定了它和传统数据库是有很多显著区别的。
读时模式和写时模式
传统数据库的表模式是有严格定义的,而且在数据加载时会验证数据是否符合模式的要求,如果不符合则会拒绝载入数据。比如将一个字母插入到数值字段时,数据库服务器会报错从而导致操作失败。我们称此模式为“写时模式”。
Hive则采用了不同的模式。它对数据的验证不是发生在数据载入阶段,而是发生在数据读取阶段。这就意味着,载入一个不符合模式的数据文件不会发生错误,但是一旦你执行譬如select语句时,则会发生错误。我们称这种方式为“读时模式”。
以上两种方式的优点和缺点都很明显。比如读时模式在数据载入时非常快。而写时模式则可以对字段进行索引,从而显著提高查询效率。
更新,事务和索引
更新,事务和索引是传统数据的重要特性,但是Hive目前还不支持这几种特性。
后续的Hive版本中会充分考虑这些特性。Hbase和Hive的集成正是了解这些特性的很好的例子。我们在Hbase中再做详细介绍。
HiveQL
Hive所使用的SQL语言我们称之为HiveQL,但是它并不完全支持SQL-92规范,毕竟Hive和传统的数据库系统是有不同应用场景的。再者,如果你发现SQL-92支持的某项功能HiveQL不支持,你也可以基于现有的HiveQL采用一些变通的方式来达成。
数据类型
Hive提供了基本数据类型和一些复杂数据类型。
基本数据类型包括:TINYINT,SMALLINT, INT, BIGINT, FLOAT, DOUBLE, BOOLEAN, STRING, BINARY, TIMESTAP。
复杂数据类型包括:ARRAY,MAP,STRUCT。
以上数据类型大部分通过字面意思即可知晓,我们不做详述,稍后的实例中我们展开其用法。
数据类型转换
和其它编程语言类似,Hive会在需要时执行一些自动转换操作。比如一个表达式期望一个int,而实际提供的值是tinyint,则hive会自动将tinyint转换为int。但是反过来,将int转化为tinyint这种隐式转换hive是不会发生的。除非你进行显式转换,比如CAST('1' AS INT)。
复杂数据类型
Hive提供了三种复杂数据类型:array,map, struct。
比如以下语句:
CREATE TABLE complex (
col1 ARRAY<INT>,
col2 MAP<STRING, INT>,
col3 STRUCT<a:STRING, b:INT, c:DOUBLE>
);
//TODO
操作与函数
Hive提供了常用的SQL操作,比如等值判断x=’a’,空值判断x isnull,模式匹配 x like ‘a%’等。
Hive提供了丰富的内置函数。你可以通过showfunctions来获取函数列表。
当然,你也可以自己编写自定义函数。稍后讲解。
表
和传统数据库表的概念类似,hive表由表数据和描述表结构等的元数据组成。表数据一般存储在HDFS中,当然也可以存在在其他文件系统,比如S3中;表的元数据存储在一个关系型数据库系统中,比如MySQL,而不是存储在hdfs中。
注意:
传统的关系数据库支持多数据库,比如它们都提供了create database语句。Hive早期版本不提供此功能,即所有表都存放在default数据库中。而最近几个版本的hive已经支持多数据库,即你也可以在hive中使用create database语句创建数据库,使用use语句却换当前数据库。
托管表和外部表(Managed table,External table)
默认情况下,当你创建Hive表时,hive将复杂管理表数据,即Hive会把表数据存储到它的数据仓库目录下(warehouse directory)。这种方式创建的表我们称之为托管表(managed table)。
另一种方式是创建一个外部表(External table)。此时,我们只需要告诉hive数据的外部引用地址,Hive本身不会在自己的数据仓库目录下存储这些数据。
以下命令创建一个托管表并加载数据:
create table student(classNostring, stuNo string, score int) row format delimited fields terminated by ',';
load data local inpath '/home/user/input/student.txt'overwrite into table student;
执行以上命令时,hive将本地文件系统中的student.txt数据文件复制到hive的数据仓库目录下。此例中,数据文件地址为hdfs://localhost:9000/user/hive/warehouse/student/student.txt
此时,如果你执行以下语句:
DROP TABLE student;
Student表将会被删除,包括表数据,元数据(metadata)。
注意:
关于元数据的存储,默认情况下Hive使用内嵌的Derby数据库来存储。
在配置文件中你可以看到:
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:derby:;databaseName=metastore_db;create=true</value>
<description>JDBC connect string for aJDBC metastore</description>
</property>
注意红色部分。元数据是存放在当前目录下的,这就意味着以下场景会发生:
第一步:当前工作目录为/root/A。进入hive shell后,hive会在/root/A目录下创建元数据。
第二步:创建表student。
第三步:退出hive。
第四步:更改当前工作目录为/root/B,再次进入hiveshell。
第五步:使用命令show tables。你会发现先前创建的student表并不存在。原因就是配置文件中,hive的元数据是存储在当前目录的metastore_db数据库中的。这一步hive又会在/root/B目录下创建一个新的metastore_db数据库。
如果你希望将metastore存储在固定位置,则你需要更改配置如下,此时无论你当前工作目录是什么,hive都会读取这个固定目录下的metastore数据库:
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:derby:;databaseName=/opt/hive-0.11.0/metastore_db;create=true</value>
<description>JDBC connect string for aJDBC metastore</description>
</property>
以下命令创建一个外部表:
Create external tableteacher(classNo string, teacherName string) row format delimited fields terminatedby ',' location '/user/hive/external/teacher';
注意:
location后面跟的是目录名,而不是文件名。Hive会将整个目录下的文件加载。目录为hdfs下的目录而不是本地文件系统的目录。(因为此例中hive是工作在hdfs文件系统上的)
执行以下语句:
LOAD DATA INPATH '/user/hive/external/teacher_02.txt' INTO TABLE teacher;
注意:
此语句将会把/user/hive/external/teacher_02.txt'文件移动到/user/hive /external/teacher/目录下;如果源数据在本地文件系统,则需要在INPATH前加上LOCAL选项,此时Hive会将源数据复制到表目录下。
执行以下语句删除外部表:
Drop table teacher;
以上语句只会删除表的元数据(metastore),不会删除表数据文件本身(本例中为teacher.txt和teacher_02.txt)。
分区和桶(Partitions,Buckets)
Hive通过分区(partitions)来组织表。这是一种通过分区列(partition column,比如日期)来对表进行划分的粗粒度管理方式。
表或分区可以进一步划分为桶(buckets)。桶为表数据提供了额外的结构信息,以提高查询的效率。比如按用户Id进行桶的划分,能显著提高基于用户ID的查询效率。
分区
假设我们有大量的日志文件,每一个日志记录包含一个时间戳。我们以日期来对它进行分区,这样的话,同一天的日志记录就会被存放在同一个分区里。这就使得我们在查询某一天(或多天)的日志记录时能够获得非常高的查询效率,因为hive只需要扫描特定的分区文件。
表的分区可以基于多个维度。比如,在按日期进行日志记录分区的同时,再进一步根据日志类型进行子分区(subpartition)。这样,当我们需要查询特定类型的日志记录时,就能够获得更高效率的查询。
运行以下命令
Create external table yarnlog (createdtime string,category string,message string) partitioned by (date string,type string) rowformat delimited fields terminated by '\t';
注意:
Partitioned by子句紧跟在createtable后面。
Hive为yarnlog表创建了5个字段,分别为createdtime,category,message,date,type。可以用describeyarnlog来查看。
运行以下语句加载数据:
load data local inpath'/home/user/input/log/20140111/info' into table yarnlog partition(date='2014-01-11',type='INFO');
load data local inpath '/home/user/input/log/20140111/warn'into table yarnlog partition(date='2014-01-11',type='warn');
load data local inpath'/home/user/input/log/20140112/warn' into table yarnlogpartition(date='2014-01-12',type='warn');
load data local inpath'/home/user/input/log/20140112/info' into table yarnlogpartition(date='2014-01-12',type='INFO');
注意:
以上语句中的红色部分。加载数据时,必须明确指明partition的各个字段值。Hive不能从数据文件中获得任何partition字段值,因为load操作只复制数据文件而不会读取文件内容。
加载完成后,hdfs中的/user/hive/warehouse目录下的子目录结构如下:
Yarnlog/
---------date=2014-01-11/
-------------------------------type=INFO/
-------------------------------type=warn/
---------date=2014-01-12/
-------------------------------type=INFO/
-------------------------------type=warn/
执行语句show partitions yarnlog,输出如下:
date=2014-01-11/type=INFO
date=2014-01-11/type=warn
date=2014-01-12/type=INFO
date=2014-01-12/type=warn
分区列(partition column)是表的正式列,你可以像使用其他列一样使用它。比如运行以下语句:
select message from yarnlog where type='warn';
此语句执行时仅仅扫描特定的分区文件。
备注:
以上使用到的日志文件格式为(tab分隔,你可以自己造一些测试数据):
2014-01-1208:51:04,818 WARN Startingcontainer_1389456842640_0006_01_000002
桶
使用桶的原因有两个:提高特定条件下的查询效率;更高效的执行取样操作。
假如有两个表按照字段uiserId进行join操作。Hive需要根据左表的某一个特定的userId来查找右表中关联的记录。如果这两个表都按照相同的方式进行了桶划分,则此时Hive在做匹配操作时,针对左表每一个特定的userId,只需要扫描右表的一个桶数据就可以了。(这么讲解可能不太恰当,因为实际操作是由Hadoop的map操作来完成的)
执行以下语句:
create table student_bucketed (classNostring, stuNo string, score int) clustered by (classNo) sorted by (classNo)into 4 buckets row format delimited fields terminated by ',';
set hive.enforce.bucketing =true;
insert overwrite tablestudent_bucketed select * from student;
以上语句中的sorted by子句是可选的,该子句申明桶为排序桶,可以进一步提高map端的连接效率。桶的划分是根据列值的hash值对桶个数的求余而来。
set hive.enforce.bucketing =true语句的作用是告诉Hive,Reduce的个数是和桶的个数一致的。如果不设置此属性值,则需要用户指定mapred.reduce.tasks属性值。
执行以下语句对表数据取样,返回大概三分之一的桶数据:
hive> select * fromstudent_bucketed tablesample(bucket 1 out of 3 on classno);
存储格式(storage fromate)
Hive存储格式涉及到两个方面:行格式(rowformat)和文件格式(file format)。
创建表的时候如果没有明确指明格式,则hive采用的默认格式为:文本文件,每行一个数据行。
Hive默认的行内分隔符不是tab,而是Ctrl+A(对应的ascii码为1)。
Hive默认的集合内分隔符是Ctrl+B,用于分隔array,map以及struct中的键值。
表中各行以换行符分隔。
Create table时不指定任何分隔符,则其等价于以下语句:
CREATE TABLE 表名 (表字段)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\
STORED AS TEXTFILE;
以上STORE AS TEXTFILE指明文件格式为文本文件。
二进制存储格式请参照官方文档。
导入数据
除了前面提到的load语句外,我们还可以从已有的其他hive表中加载数据到特定的表。如前面提到的语句:
insert overwrite tablestudent_bucketed select * from student;
这个语句将会查询student表中的所有数据并插入到student_bucketed表中。当然你还可以添加partition选项,这就是所谓的动态分区插入功能(dynamic-partition insert),不过你需要通过set hive.exec.dynamic.partition= true来启用该功能。
注意:
以上语句中的overwrite是必须的,这就意味着目标表(或分区)将会被覆盖。Hive目前还不支持往已有表(分区)中添加新纪录的功能。
Hive不支持insert into tablevalues() 这样的语句。
多表插入
Hive支持以下写法:
FROM 源表
INSERT OVERWRITE TABLE 目标表1
SELECT 字段1 where条件1
INSERT OVERWRITE TABLE 目标表2
SELECT 字段2 where条件2
Select创建新表
和传统的关系数据库类似,Hive支持以下写法:
CREATE TABLE 新表名
AS
SELECT col1, col2
FROM 源表;
表的修改
ALTER TABLE 表名 RENAME TO新的表名;
ALTER TABLE 表名 ADD COLUMNS (列名列类型);
更新信息请参照官方文档。
表的删除
以下语句删除表的元数据及表数据(外部表只删除元数据):
Drop table 表名
如果你只是想清空表数据而保留表的定义,你可以直接通过hdfs删除表目录下的文件即可。
以下语句将创建一个新表,其结构和已有的表相同,但是表数据为空:
CREATE TABLE 新表 LIKE已有的表;