hive介绍
什么是hive?
hive:由Facebook开源用于解决海量结构化日志
的数据统计
hive是基于hadoop的一个数据仓库工具
,可以将结构化的数据映射为数据库的一张表
,并提供类SQL
查询功能。本质就是将HQL(hive sql)转化为MapReduce程序
我们使用MapReduce开发会很麻烦,但是程序员很熟悉sql,于是hive就出现了,可以让我们像写sql一样来编写MapReduce程序,会自动将我们写的sql进行转化。但底层使用的肯定还是MapReduce。
hive处理的数据存储在hdfs
hive分析数据底层的默认实现是MapReduce
执行程序运行在yarn上
hive的优缺点
优点:
操作接口采用类SQL的语法,提供快速开发的能力(简单、容易上手)
避免了去写MapReduce,减少开发人员的学习成本
hive的执行延迟比较高,因此hive擅长于数据分析、对实时性要求不高的场合
还是因为hive的延迟比较高,使得hive的优势在于处理大数据,对于处理小数据没有优势
hive支持用户自定义函数,用户可以根据自己的需求来实现自己的函数
缺点:
-
hive的HQL表达能力比较有限
1.迭代式算法无法表达
2.数据挖掘方面不擅长
-
hive的效率比较低
1.hive虽然自动地生成MapReduce作业,但是通常情况下不够智能化
2.hive调优比较困难,粒度较粗
hive的架构
1.用户接口:Client
CLI(hive shell),pyhive(python访问hive),WebUi(浏览器访问hive)
2.元数据:Meta store
元数据包括:表名、表所属的数据库(默认是default)、表的拥有者、列/分区字段、表的类型(是否是外部表)、表的数据所在目录等等
3.hadoop
使用hdfs进行存储,使用MapReduce进行计算
4.驱动器:Driver
1.解析器(sql parser):将SQL字符串转成抽象语法树(ast),这一步一般都用于第三方工具完成,比如antlr,然后对ast进行语法分析,比如表是否存在、字段是否存在、逻辑是否有误等等
2.编译器(physical plan):将ast编译生成逻辑执行计划
3.优化器(query optimizer):对逻辑执行计划进行优化
4.执行器(execution):把逻辑执行计划转化为可以运行的物理计划。
hive和数据库的比较
由于hive采用了类似于SQL的查询语言HQL(hive query language),因此很容易将hive理解为数据库。其实从结构上看,hive除了和数据库拥有类似的查询语言,再无相似之处,下面我们来看看两者的差异
-
查询语言
由于SQL被广泛的运用在数据仓库中,因此,专门针对hive的特性设计类SQL的查询语言HQL,熟悉SQL开发的开发者可以很方便的使用hive进行开发
-
数据的存储位置
hive是建立在hadoop之上的,所有的hive数据是存储在hdfs中的,而数据库则是可以将数据保存在块设备或本地文件系统中的
-
数据更新
由于hive是针对数据仓库应用设计的,而数据仓库的内容是读多写少的。因此hive不建议对数据进行改写,所有的数据都是在加载的时候确定好的。而数据库中的数据通常是需要进行修改的。
-
索引
hive在加载数据的过程中不会对数据进行任何处理,甚至不会对数据进行扫描,因此也没有对数据中的某些key建立索引。hive要访问数据中满足条件的特定值时,需要暴力扫描整个数据,因此访问延迟较高。而由于MapReduce的引入,hive可以并行访问数据,因此即使没有索引,对于大数据量的访问,即使没有索引,hive仍可以体现出优势。数据库中,通常会针对一个或几个列建立索引,因此对于少量的具有特定条件的数据访问,数据库可以有很高的效率,较低的延迟。由于数据的访问延迟较高,决定了hive不适合在线数据查询。
-
执行引擎
hive中大多数查询的执行是通过hadoop提供的MapReduce实现的,而数据库通常有自己的执行引擎
-
执行延迟
hive在查询数据的时候,由于没有索引,需要扫描整个表,因此延时较高。另外一个导致hive执行延迟高的因素是MapReduce框架,由于MapReduce本身具有较高的延迟,因此在利用MapReduce执行hive查询时,也会有较高的延迟。相对的,数据的执行延迟较低,当然这个低是有条件的,即数据的规模较小。当数据的规模大到超过了数据库的处理能力的时候,hive的并行计算显然能够体现出其优势。
-
可扩展性
由于hive是建立在hadoop之上的,所以hive的可扩展性和hadoop是一样的(世界上最大的hadoop集群在Yahoo!,2009年的规模在4000台节点左右)。而数据库由于ACID语义的严格限制,扩展性非常有限。目前最先进的并行数据库Oracle在理论上的扩展能力也只有100台左右
-
数据规模
由于hive建立在集群上并可以利用MapReduce进行并行计算,因此可以支持很大规模的数据。对应的,数据库支持的数据规模较小
hive的安装
由于hive是Apache的顶级项目(Facebook交给了Apache),所以直接官网hive.apache.org
下载,首先说明一下,安装hive之前肯定要安装jdk和hadoop,关于这两者的安装我在介绍hadoop的那篇博客中
已经说了,这里不再赘述,下面我们就去官网安装hive,hive我们安装2.3.6版本,和我阿里云上2.7.7版本的hadoop是匹配的。
我的hive会安装在/opt/hive目录下,所有的大数据组件都是这样的,java的话就是/opt/java,hadoop就是/opt/hadoop
先来看看hive的目录结构
bin目录是用来执行一些命令的,我们注意到没有sbin目录,sbin目录一般是存放启动文件的,就像hadoop的sbin一样,显然对于hive是将两者合并了到bin目录里面了。conf显然是存放配置的,examples,案例,等等。
修改配置文件
hive-env.sh
在hadoop中我们说了,返回以-env.sh结尾,基本上都是配置路径,当然对于hive来说,我们要配置HADOOP_HOME和HIVE_CONF_DIR,因为hive是基于hadoop的嘛。当然conf目录下没有hive-env.sh,只有hive-env.sh.template,老规矩,需要先cp hive-env.sh.template hive-env.sh。然后,因为文件时注释掉的,我们直接在末尾添加即可,HIVE_CONF_DIR就是我们刚才说的conf目录的绝对路径
export HADOOP_HOME=/opt/hadoop/hadoop-2.7.7
export HIVE_CONF_DIR=/opt/hive/apache-hive-2.3.6-bin/conf
另外如果你的hadoop都已经配置了环境变量,那么这些配置也可以不用改,可以自动找到,但是还是建议配一下。
修改完毕,由于我已经配置了环境变量,所以在家目录启动一下hive,直接输入hive即可,如果没有配置环境变量,那么需要进入到bin目录里面
执行成功,但是当我们show databases;
的时候报错了,为啥嘞,显然是没有数据库啊,不过至少目前hive是启动成功了。
下面我们就来安装mysql
1.wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
2.rpm -ivh mysql-community-release-el7-5.noarch.rpm
3.yum install mysql-server(如果失败的话,可以先执行一下yum update)
4.mysqld --initialize
5.systemctl start mysqld
6.输入:mysqladmin --version,如果有类似如下内容说明安装成功
mysqladmin Ver 8.42 Distrib 5.6.45, for Linux on x86_64
7.设置密码:mysqladmin -u root password 你的密码
8.mysql -u root -p进入数据库
大功告成。
下载驱动配置mysql
安装驱动
我们安装好了mysql,但是java要连接是不是要需要驱动呢?是的,所以我们还需要单独下载驱动,至于驱动的下载地址,随便百度一搜java连接mysql驱动
就能找到,这里我们可以使用菜鸟教程提供的https://www.runoob.com/java/java-mysql-connect.html
,进去会看到页面所提供的下载路径
下载完毕之后,直接扔到HIVE_HOME的lib目录里面去就行了
配置mysql
hive默认使用的derby,我们要配置成mysql。显然要修改配置文件,这里我们修改conf目录下的hive-site.xml
,但是没有这个文件,所以我们要touch hive-site.xml
,然后打开在里面输入如下内容:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!-- 指定mysql -->
<property>
<name>javax.jdo.option.ConnectionURL</name>mysql
<value>jdbc:mysql://localhost:3306/metastore?createDatabaseIfNotExist=true</value>
</property>
<!-- 指定mysql的驱动 -->
<property>
<name>javax.jdo.option.ConnectionDriverName</name>mysql驱动程序
<value>com.mysql.jdbc.Driver</value>
</property>
<!-- 指定mysql的用户 -->
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>root</value>
</property>
<!-- 指定mysql的密码 -->
<property>
<name>javax.jdo.option.ConnectionPassword</name>密码
<value>密码</value>
</property>
<property>
<name>hive.metastore.schema.verification</name>
<value>false</value>
</property>
<property>
<name>datanucleus.schema.autoCreateAll</name>
<value>true</value>
</property>
</configuration>
重新启动hive
启动成功之后,可以看到metastore这个数据库已经自动为我们创建了
随便测试一下
我们通过webUI的方式查看一下
我们把那个000000_0的文件下载下来看一看
这不就是我们之前存的内容嘛。
python连接hive
python若想连接hive,需要下载第三方包。
pip install sasl
这个如果直接pip安装的话其实不出意外会报错,因为有C或者C++扩展,我们需要去https://www.lfd.uci.edu/~gohlke/pythonlibs/这个网站去下载对应python版本的sasl,然后安装就可以了。至于下面三个,直接pip 安装即可
pip install thrift
pip install thrift_sasl
pip install pyhive
from sqlalchemy import create_engine
engine = create_engine("hive://47.94.174.89:10000/default")
# 就把hive当成一个数据库使用就行,也是支持sqlalchemy的,当然底层使用pyhive
import pandas as pd
df = pd.read_sql("select * from girls", engine)
golang连接hive
golang若想连接hive,同样需要下载第三方包。go get github.com/lwldcr/gohive
package main
import (
"fmt"
"github.com/lwldcr/gohive"
"os"
)
func main() {
//创建连接
client, err := gohive.NewTSaslTransport("47.94.174.89", 10000, "", "", gohive.DefaultOptions)
if err != nil {
fmt.Println("连接出错,错误信息:", err)
os.Exit(1)
}
//打开socket,启动sasl客户端,发送初始信息,打开session会话
//这一步看似只返回了一个error,但是必须要有,否则无法查询
if err := client.Open(); err != nil {
fmt.Println("open failed:", err)
os.Exit(1)
}
defer client.Close()
// client.Query执行查询
rows, err := client.Query("select * from girls")
if err != nil {
fmt.Println("查询出错,错误信息:", err)
os.Exit(1)
}
defer rows.Close()
//打印所有列
fmt.Println(rows.Columns()) // [girls.id girls.name girls.age girls.place]
type Girl struct {
id int
name string
age int
place string
}
girls := make([]Girl, 0)
//迭代
for rows.Next() {
girl := Girl{}
//Scan
if err := rows.Scan(&girl.id, &girl.name, &girl.age, &girl.place); err != nil {
fmt.Println("获取数据出错,错误信息:", err)
os.Exit(1)
}
girls = append(girls, girl)
}
fmt.Println(girls) // [{1 nagisa 21 clannad} {2 mashiro 16 sakurasou} {3 yuuko 16 ef}]
}
hive数据类型
基本数据类型
集合数据类型
下面我们就来演示一下,怎么使用这些数据结构。
我们就定义四个字段,name,hobby,info,score,解释一下。
name:姓名,显然是string类型,也可是varchar
hobby:兴趣,一个人可以有很多兴趣,所以这是一个array
info:个人信息,比如身高->int,性别->string,这是一个结构体
score:成绩,语文:90,数学:89,英语:92,这是一个map
那么怎么定义呢?
create table student (
name string,
hobby array<string>,
info struct<length: int, gender: string>,
score map<string, int>
)
--还没完,还有一个很重要的事情就是分割符
--对于每一个字段来说,我们在插入数据的时候,是使用逗号分割
--但是对于集合来说,有多个元素,我们要怎么分开呢,因此我们需要指定分割符
--字段之间,使用逗号分割
row format delimited fields terminated by ','
--对于集合来说,我们使用_分割,
collection items terminated by '_'
--比如hobby就是 唱歌_跳舞
--info就是160_女
map keys terminated by ':'
-- 对于map,我们使用:,那么score可以是 sum:285
lines terminated by '
';
--也可以不用指定,多行默认使用
分割
我们把注释去掉,执行一下
插入几条数据吧,这个要放在文件里面。就放在/root/student.txt
里面,我们注意到结构体和数组都是使用同一个分隔符
椎名真白,吃饭_画画,160_女,语文:89_数学:90_英语:90
古明地觉,宠物_古明地恋,155_女,语文:88_数学:95_英语:90
然后通过load data local inpath "文件名" into table 表名
看一下数据在hive里面长什么样
获取数组的第二个元素,获取struct的length这个key,获取map的'数学'这个字段
hive类型转换
hive的原子数据类型是可以进行隐式转换的,比如某表达式使用int类型,那么tinyint会自动转化为int,但是不会进行反向转化。比如int不会转为tinyint,会返回一个错误,除非使用cast显式转化
-
隐式类型转化规则如下
1.任何一个数据类型都可以隐式的转化为一个范围更广的类型,如tinyint可以转化为int,int可以转化为bigint
2.所有的整数类型、float、string类型(如:'12.11')都可以隐式的转为double
3.tinyint、int、bigint都可以转化为float
4.boolean类型不可以转化为其他任何的类型
-
可以使用cast操作显示进行数据类型转换
例如cast('1' as int)将字符串'1'转化为整数1;如果强制类型转化失败,如执行cast('xxx' as int),表达式返回null。
数据库的增删改查
hive初始只有一个默认的数据库,就是default,我们可以也可以自己创建、删除、修改、查找数据库。
创建数据库
首先数据库在hdfs上的默认存储路径是/user/hivewarehouse/
创建数据库语法:create database db_hive
,表示创建db_hive
这个数据,但如果db_hive
已经存在的话会报错,因此更严格一点的写法是create database if not exists db_hive
。
但是我们创建的数据库在哪呢?之前说了,都在/user/hivewarehouse/
下面,我们来看看
可以看到我们在default数据库中建的表也在/user/hivewarehouse/
下面,建的数据也是一样的。因此为了区分,会自动在我们建的数据库名后面加上一个.db
,来区分这是一个数据库。我们在db_hive数据库中建一张表看看,那么在db_hive数据库里面建立的表,就在/user/hivewarehouse/db_hive.db
路径下面了
问题来了,我们创建数据库的能不能自己指定位置呢?显然是可以的,加上一个location 路径
即可
我们创建了一个名为komeijisatori
的数据库,但是路径在/
下面,并且指定了新名字komeijisatori123
查询数据库
这个就没必要介绍了吧,只是hive中查询数据库还支持模糊查询。
这里只显示以'd'开头的数据库
还可以查看数据库信息,desc database 数据库;
,显示详细信息的话desc database extended 数据库
修改数据库
用户可以使用alter database
来为某个数据库设置键值对属性值,来描述这个数据库的属性信息。数据库的其他元数据信息都是不可更改的,包括数据库名和数据所在的位置
,只能改一些附加的信息,比如创建时间等等,这里不介绍了,没啥太大卵用。
删除数据库
drop database 数据库
,可以看到基本上和mysql的语法差不多。如果某个数据库删不掉,那么可以使用drop database 数据库 cascade
强制删除
建表语法
最基本的建表语法,如下:
create table 表名 (
列名 类型 [comment 注释],
列名 类型 [comment 注释],
列名 类型 [comment 注释],
......
);
--[comment 注释]表示可加可不加。
--除此之外我们还有很多可加可不加的选项。
create [external] table [if not exists] 表名(
列名 类型 [comment 注释],
列名 类型 [comment 注释],
列名 类型 [comment 注释],
......
)
[comment 表注释]
[partitioned by (列名 类型 [comment 注释], ···)]
[clustered by (列名,列名,列名,···)]
[sorted by (列名[asc|desc], ···) into num_buckets buckets]
[row format row_format]
[stored as file_format]
[location hdfs_path]
我们先简单解释一下,后续会详细介绍:
external:表示创建一个外部表,不加表示创建内部表
[comment 标注释]:除了给字段添加注释,还可以给表添加注释
partitioned by:按照指定字段分区
clustered by:按照指定字段分桶
sorted by:按照字段排序,很少用
row format:按照···分割
stored:按照指定格式存储,默认是txt
location:之前说了,指定hdfs存储路径
内外部表
内部表和外部表的主要区别就在于删除方面。我们默认的创建的表都是内部表,也叫管理表。当我们删除一个表的时候,实际上删除的是数据库里面的元数据,而原始的数据信息存在hdfs上面。如果是内部表,hive会控制生命周期,在删除元数据的时候,hdfs上数据也会删除。但如果是外部表,那么删除元数据,不会影响hdfs上的原始数据,因为两者是不同的对象管理,我们来演示一下。
我们就来删除student这张表,这张表我们创建的时候没有指定external
,所以这是一张内部表,我们删除一下。
可以看到连同hdfs上的数据一同被删除了。我们再来试一下外部表
创建的是外部表的话,即使删除了元数据,hdfs上的原始数据依旧坚挺在那里
那么我们如何选择呢?如何只是作为中间的临时表,建议使用内部表,否则还要手动删除hdfs上的数据,如果是一直使用的表,那么建立外部表,这样更安全。另外hive是如何查询数据的呢?hive是通过mysql存储的元数据信息然后找到对应的存储数据的hdfs路径,这两者无论先有哪一个都无所谓。我们可以先有元信息,然后在指定的hdfs路径上存数据;也可以先有数据,然后再把数据存储的路径作为元信息存储到mysql中,都是一样的,这两者没有所谓的先后顺序。
我们绝大部分建立的都是外部表,内部表只有在作为中间临时表才会用。
但是问题来了,如果我们创建表的时候不小心创建错了,本来想创建外部表结果创建成了内部表该怎么办呢?其实hive是支持我们修改的
-
查看表的类型
告诉我们这是一张管理表(内部表)
-
修改表的类型
alter table 表名 set tblproperties('EXTERNAL'='TRUE')
,表示把表变为外部表,同理FALSE的话,是把表变为内部表。注意:EXTERNAL、TRUE、FALSE
这三者是固定写法,区分大小写,不能用小写成功修改为外部表
分区表
分区表实际上就是对应一个hdfs文件系统上的独立的文件夹,该文件夹下面是该分区所有的数据文件。hive中的分区就是分目录,把一个大的数据集根据业务需要分割成多个小的数据集。然后在查询的时候指定分区,会提高效率。怎么理解呢?我们举个例子:
heroes是我们的一张表,但是你会发现在hdfs上面是一个目录,heroes目录里面存放的是具体的数据文件(当然现在还没有数据),点击就可以下载了。之前我们说过hive是没有索引的,因此在查询数据的时候,是暴力的全表扫描。而分区表的话,表示按照字段进行分区,比如按照时间进行分区,1号、2号、3号,那么等于说是按照时间把表分成了三个区域,这三个区域当然还在heroes里面,但是它们不再是具体的数据文件,而还是一个目录(分区目录),然后每个目录里面存储各自对应的分区数据。那么当我想查找对应的数据,就只需要去对应的分区(比如1号的数据,只需要到1号分区里面去查找)
就可以了,就不用全表扫描了。尤其是当数据量很大的时候,指定分区表是非常有必要的,其实我们在生产中建立的表绝大部分都是分区表。
比如365天的数据,如果不指定分区,那么heroes里面可能会有365个文件,那么当查找的时候会从这365个文件中查找,即便我们只想从第200的数据中查找。但如果是分区表就不一样了,我们可以按照天分区,如果数据量增长速度极快,那么你还可以按照小时分区。按照天分区的话,heroes里面就相当于有365个分区目录,每个目录里面存放了对应的数据,那么当我们想要第200天的数据的时候,只需要到第200天对应的分区里面去找就ok了
下面我们就来创建一下分区表,并插入数据
可以看到我们的分区表已经创建好了,但是在插入数据的时候报错了,报了个什么错误呢?告诉我们因为目标表被分区了,我们需要指定分区列
补充一些,创建表的时候,分区字段不能出现在定义的表的字段里面。我们通过指定day='2018-2-14',表示这是一个day='2018-2-14'的分区。比如今天是2019-3-3,那么我想把今天的数据存在一个分区里,就可以指定partition(day='2019-3-3'),表示这个分区存放了2019-3-3的所有数据。
此时我们再使用webUI查看一下,发现里面多了一个目录,注意这是目录,如果不是分区表,那么就是文件。我们点进去看看
我们点进去就能看到我们load进来的文件,所以分区分的就是文件夹(目录)。那比如说,我们再创建一个分区
可以看到就又多了一个分区。
那么我们就来查询一下数据,当然我们dt也是日期格式的,不过这个dt和month没啥关系。两个分区存的数据是一样的,都是10000条
我们原来的数据只有三个字段,但是现在是4个,说明把分区字段day
也给加上去了,由于是partition(day='2018-2-14'),所以添加的day对应记录都是2018-2-14
,当然由于我们只选了10条,这10条都是分区day='2018-2-14'下面的
我们查看总共的记录数是20000,说明默认把两个分区都给算进去了,当然查询其他内容,也是一样的,默认是所有分区。但如果我们想指定分区呢?我们创建了两个分区,day='2018-2-14'和day='2018-7-15',我想查day='2018-7-15'的分区该怎么办呢?直接当day(分区字段)当成普通字段来查询即可。
我们来查找一下记录
两个分区数据是一样的,默认全部查找,也可以指定分区在对应分区里面查找。
那么hive是如何做到的呢?我们之前说了元数据都存在mysql里面,而我们指定的数据库是metastore,我们就进去看看,里面到底存了哪些表?
可以看到里面存了很多表,其中的TBLS存储的就是hdfs数据的元信息,而且我用箭头还指向了PARTITIONS
这张表,不用想,从名字就能看出来这是与我们现在的分区有关系,我们来看看这张表长什么样子
看到了没,分区字段也是数据的元信息,是存储在mysql里面的,通过分区信息来找到对应的分区。所以我们是先找到分区,然后再到分区里面查找数据,所以说hive虽然没有索引,但是这个分区是不是也有点索引的感觉呢?不过分区的这个字段它并不是我们在表中真正意义上创建的字段(所以才说指定的分区字段名不能和我们定义的表的字段名之间有重复,也就是不能用表的字段再作为分区的字段)
,这只是作为元信息。不过你可以认为它是我们普通地定义的表的字段之一、并在查询的时候把所有的字段一视同仁,毕竟在查询的时候分区字段也会跟着一块出来。
package main
import (
"fmt"
"github.com/lwldcr/gohive"
"os"
)
func main() {
client, err := gohive.NewTSaslTransport("47.94.174.89", 10000, "", "", gohive.DefaultOptions)
if err != nil {
fmt.Println("连接出错,错误信息:", err)
os.Exit(1)
}
if err := client.Open(); err != nil {
fmt.Println("open failed:", err)
os.Exit(1)
}
defer client.Close()
rows, err := client.Query("select * from info")
if err != nil {
fmt.Println("查询出错,错误信息:", err)
os.Exit(1)
}
defer rows.Close()
fmt.Println(rows.Columns()) // [info.id info.str info.dt info.day]
}
我们使用golang,也是能够获取day这个字段的
分区表基本操作
添加分区:
我们之前是通过load data local inpath
,然后指定分区,从而创建了分区并且把数据导入了对应的分区中,那么可不可以直接创建分区呢?显然是可以的,通过alter table info add partition(day='2018-9-9') partition(day='2018-9-10') ...
操作,是的,我们可以一次性指定多个分区,当然也可以一次只指定一个。
可以看到多了三个分区,当然里面都没有数据。
我们注意到这个/user/hive/warehouse/info/day=2018-9-9
,里面是没有数据的。我们之前可以通过load data local inpath
这种方式将数据增加到分区里面的。那么通过手动上传文件的方式,将数据上传到hdfs上面,然后通过sql能不能访问呢?我们来试一试
数据是上传成功了的,但是能不能通过sql访问呢?
显然是可以的,所以不管先后,不管是先有元数据、还是先有原始数据,只要有都能访问到。
删除分区:
alter table info drop partition(day='2018-9-9'),partition(day='2018-9-10')
,注意到和创建分区不同,除了把add换成了drop之外,多个分区之间是使用逗号分隔的,这个比较恶心,你要使用空格都使用空格,使用逗号都使用逗号。但是创建多个分区,使用空格分隔,删除多个分区,使用逗号分隔。
之前创建的分区也没了。
查看分区:
show partitions table
,查看分区表有多少分区。desc formatted table
查看分区表结构
分区表注意事项
创建二级分区:
不用想,创建二级分区,肯定是指定两个分区字段。就跟普通字段一样,分区字段也可以有多个。
create table hahaha (
id int, name string, age int
)
partitioned by (month string, day string)
row format delimited fields terminated by ' ';
我们看看hdfs上的目录是什么样子的,我们知道一级分区是一个目录,那么二级分区是不是目录里面还有一个目录呢?答案是是的。
我们多创建几个二级分区看看。
此时就有两个分区了。
而且也能看到,查询数据的时候和一级分区表现出来的性质是一样的。如果只有一级分区,那么在不指定分区的时候会获取所有数据。有二级分区,但是查询的时候只指定了一级分区,那么会默认获取一级分区下的所有二级分区的数据。当然如果一级、二级分区都不指定,那么默认还是会获取所有的数据。
加载二级分区的数据:
这个就无需介绍了,因为我们刚才已经演示过了,和一级分区加载数据一样,只不过在partition中多指定了一个分区。查询数据和一级分区也是类似的,也已经介绍了,不再赘述
修复数据
我们通过hdfs dfs命令手动在hdfs上创建一个目录,然后把数据放进去,但是会发现查询不到。这是为啥?之前说过很多遍了,分区是数据元数据的信息,把数据上传了,但是没有和元数据关联。而且要想查询,元数据和真实数据缺一不可,但是不分顺序。因此可以使用如下办法:
-
上传数据后修复
可以执行修复命令,
msck repair table 表名
,会自动将hdf上有的分区而元数据里面没有的,自动帮你补上。 -
手动添加元数据
我们可以手动往元数据里面添加分区信息。至于我们之前为什么能够关联,那是因为我们创建表啊、load data local inpath啊,会自动地帮我创建分区。
修改表
重命名表:
alter table 表名 rename to 新表名
增加、修改、删除表分区:
上面介绍了,可以回顾一下
增加、修改、替换列信息:
-
增加列
alter table 表名 add columns (列名 类型, 列名 类型 ...)
,既然是columns,肯定是可以添加多列 -
修改列
alter table 表名 change column 列名 新列名 类型
,可以只改名字,也可以只改类型,当然新的列名和类型必须都写 -
替换列
这个比较怪异,
alter table 表名 replace columns (列名 类型, 列名 类型)
,这个替换
表示的是把原来表的列全部清空,然后把指定的新的列添加进去。比如原来的表有id,name,如果是replace columns (uid int, age int)
,那么此时的表就只有uid 和 int。所以比较让人难以理解,是把原来的所有列全删了,把新指定的列加进去。如果是替换单个列,就用change。但是注意的是我们改的是元数据,实际数据依旧存储在hdfs上面,是不会变的。
使用load方式加载数据
load data [lcoal] inpath '/xx/xx.txt' overwrite|into table 表名 [partition(a=xx, b=xx)]
load data:表示从加载数据
local:表示从本地加载到hdfs表,否则从hdfs上加载
inpath:表示数据的加载路径
overwrite:表示覆盖表中已有数据,否则追加
into table:表示加载到哪张表
partition:表示上传到指定分区
使用insert和as select加载数据
表存在时:
我们之前一直使用insert插入数据,显然是可以的
但是除了insert之外,还可以使用select,insert into 表1 select * from 表2
,表示将表2的记录插入到表1当中,当然可以筛选字段、指定行数
记录是一样的
如果是insert overwrite,那么会将原来表的记录清空,再插入。insert into则是直接追加
表不存在时插入数据:
这个相当于是创建表的同时指定数据,create table 表名 as select * from 表
girls表里面的内容原封不动的进入girls2表里面了,注意我们在创建表的时候没有指定字段、类型,因为girls2表的数据、字段、类型都会和girls表保持一致。以及我们还可以执行复杂的查询,将查询之后的结果作为一张新表。
除此之外还可以使用location
,create table 表名(列名 类型...) location "hdfs路径"
,但是不常用
数据导出
insert导出:
-
将查询的结果导出到本地
insert overwrite [local] directory "本地目录(不存在则创建)" select * from 表名
,如果不加local,则是导出到hdfs上面。当然查询不一定是select * ,还可以是其它复杂查询,这里就用select *代替了但是我们发现导出的时候没有分隔符,这样的数据貌似也不好用啊。我们在创建表的时候可以指定分隔符,表示我们通过本地数据导入的时候,本地数据的每一行的每一个字段之间用指定分隔符分隔。那么同理我们能不能在导出的时候也指定个分隔符、使得导出的数据的每一行的每一个字段之间也是用指定分隔符分隔呢?答案是可以的,语法跟创建表的时候是一样的。
这样导出的数据是不是就好看一些了呢?
导出数据的其他方式
-
hadoop命令导出
不再赘述,hadoop当中介绍过了
-
hive shell命令导出
直接在linux终端下,通过
hive -e 'select···等查询语句' > file
,这个也很简单,hive -e 查询,表示就是在不进入shell的时候执行查询,然后导入到文件里面去 -
export导出到hdfs上(不常用)
export table 表名 to 'hdfs目录'
虽然指定的yoyoyo.txt,但其实这是一个目录,里面有一个data目录,data目录里面才是我们导出的文件
export必须要和import搭配使用,import只能导入export导出的数据。并不是任何一个存在的hdfs目录,都可以import,比如是export导出的数据才可以import,注意到那个_metadata了吗?这就表示是export导出的
至于导入可以使用:
import table 表名 from "hdfs路径"
,注意:table可以不存在,但是如果存在则列的信息必须要一致。 -
sqoop
这是一个框架,不单单是一个命令,这个后续会专门开一篇博客介绍。sqoop不仅可以把文件hdfs上的数据导入到本地,还可以导入到数据库,这个是可以和你的业务进行对接的。
清空表
清空表:truncate table 表名
。删除的是hdfs上的数据,但是元数据会保留。就跟mysql一样,只删除数据、但是字段信息保留。注意:清空表只能清空内部表(管理表),外部表hive是没有权限清空的。如果视图清空一个外部表是会报错的。
查询
查询可以说是重中之重,但是我觉得学hive的基本上都会sql吧,语法和mysql很类似,因此简单的sql就不介绍了,因为实在没有必要。会挑一些介绍
where
where语句,和mysql大部分是一致的,但是个人认为hive提供的sql更强大一些,因为有几个特殊的
-
<=>
这个和=基本上是一样的,但是如果符号两边有任何一方为null,那么=返回的结果都是null,对于<=>来说,即便有null,返回结果依旧为布尔值。
-
not between 值1 and 值2
不在两个值之间的
join
和mysql一样,不再赘述。但是注意的是:hive只支持等值连接,意思就是on后面的条件,只支持=,不支持其它的符号。比如select a.id,a.name,b.id,b.name from a join b on a.id=b.id
是可以的,但是如果是on a.id > b.id
就不行,当然on后面的条件一般都是等于。
内连接:join
左连接:left join
右连接:right join
全连接:full join
当然也可以以多表连接,但是注意条件,n个表join,至少要有n-1个连接条件。但是多个连接条件之间只能用and,如果是or会报错。个人觉得这算是缺陷吧,用的时候要注意。
排序
order by 列
或者order by 列 desc
,和mysql类似,不再赘述。
分桶表创建&&导入数据
分桶表用的比较少,它和分区表一样,都是为了方便查询而使用的。只不过分区针对的是数据的存储路径,分桶针对的是数据的存储文件。
分区提供一个隔离数据和优化查询的便利方式。不过,并非所有的数据集都可以形成合理的分区。
分桶是将数据集分解成更容易管理的若干部分的另一个技术。
创建分桶表:
create table tong(id int, name string)
clustered by(id)
--分区表是partitioned by,分桶是clustered by
--但是我们发现这分桶的字段是表的普通字段里面存在的
--因此这是分桶和分区的一个很大的区别,
--分区字段相当于在原有字段的基础上多增加了一个
--分桶字段则是表字段里面已经存在的字段,所以不需要指定类型了
into 4 buckets --分成四个桶
row format delimited fields terminated by ' ';
0 asSA
1 SEcd
2 OzBE
3 tYZf
4 fopk
5 FlSB
6 AvBu
7 nMwS
8 lXzb
9 IcTz
我们将data.txt加载进来
但是报错了,意思就是对于分桶表来说,load data local inpath这种方式是不安全的,建议我们先将数据导入到中间表,然后使用 insert into xxx select
的方式导入。但是导入之前需要设置一些属性。
set hive.enforce.bucketing=true;
set mapreduce.job.reduces=-1; 这个其实可以不用设置,默认是-1,就是表的桶的数量
create table temp_table (id int, name string) row format delimited fields terminated by ' ';
load data local inpath "./data.txt" into table temp_table;
insert into tong select * from temp_table;
drop table temp_table;
创建临时表,导入数据,再导入分桶表,删除临时表。
我这阿里云不知道怎么回事,在执行到insert into tong select * from temp_table;
的时候总是hive总是自动退出,不知道啥原因。不过可以说结论,就是把id%4,结果相同的单独作为一个文件,跟分区是比较类似的。
分桶抽样查询
对于非常大的数据集,有时候需要进行抽样,也就是需要一个代表性的结果,而不是全部结果,那么分桶表很适合这种业务。
语法:select * from tong tablesample(bucket 1 out of 4 on id)
这里的tablsample(bucker x out of y on id)
是一个语法,on id
很好理解,就是基于id,y必须是table的bucket数(我们开始设置的是4)的倍数或者因子。hive根据y的大小,决定抽样的比例。例如:我们之前创建的分桶表是四个桶,那么当y=8的时候,表示4/8,抽1/2个桶。如果y=2,表示4/2,抽两个桶,至于x表示从第几个桶开始抽。但是抽是按照顺序抽的吗?不是的,是按照,x x+y x+2y···这样的顺序,比如说,我有16个桶,然后是bucket i out of 4
,那么抽的桶就是1 1+4 1+4+4 1+4+4
,即1 5 9 13
这四个桶。也正因为如此我们需要x小于等于y,否则报错。那可能会有人觉得,会不会到最后我们要取的桶的位置超过所有的桶数,比如总共16个桶,但是我们却要第17个桶,会不会出现这种结果呢?显然不会的,因为x小于等于y就决定了,不会出现这种结果。假设有z个桶,我们分y个,显然我们要z/y个,那么最后一个桶的位置就是x+(z/y - 1)*y = x+z-y,由于x小于等于y,因此最多取到最后一个桶(当x=y的时候)。
分桶表我们就简单的介绍了一下,以及它的用途,由于我服务器的原因这里就不演示了,其实也不是很重要,需要的时候再去查吧。
查询函数
下面我们介绍一些常用的查询函数。
空字段赋值
NVL:给值为null的数据赋值,它的格式是NVL(col, value)
,参数是(字段, 值)的格式,如果字段为null,那么返回指定的值。如果字段不会null,返回字段的值。如果都为null,那么就只能返回null了。
当然nvl不仅仅只(字段,值)的形式,还可以是(字段1,字段2)的形式,如果字段1的值为null,就用对应的字段2的值代替。
与时间有关的方法
-
date_format:格式化时间
-
date_add:时间跟天数相加
-
date_sub:时间跟天数相减
-
datediff:时间和时间相减,注意没有下划线
都是按照天数相减的。
-
regexp_replace:指定字符替换
如果是'2018/08/09'这种格式是没有办法转换的,在hive中只认-,那么怎么办呢?我们可以将
/
替换成-
常用函数case & if
create table aaa (
id int,
age int,
status string
);
insert into aaa(id, age) values(1, 16),(2, 26), (3, 36), (4, 46), (5, 50), (6, 60);
--语法
select 字段1,字段2,
case
when 条件 then ''
when 条件 then ''
...
...
else '' --默认值,可以没有
end --必须有
from table
或者
select 字段1,字段2,
case 字段3 --字段写在case后面,然后when后面就不能跟条件了,只能跟个值
when 值1 then ''
when 值2 then ''
...
...
else '' --默认值,可以没有
end --必须有
from table
select id, age, case
when age < 20 then '青年'
when age > 20 and age < 40 then '壮年'
when age > 40 and age < 50 then '中年'
else '老年' end
from aaa;
select id, case id
when 1 then '一'
when 2 then '二'
when 3 then '三'
else '大于三' end
from aaa;
注意到:我们这里的case when
可以写很多个,但是如果是而二分类的话呢,我们可以用if,当然继续用case when也没什么影响,只是对于这种分类情况比较少、只有两种的情况,我们完全可以使用if
替代
select id,
if (id <=3, '小于等于3', '大于3') as is_smaller_3
from aaa;
无需解释,看一下就明白了。
常用函数行转列
concat:
concat('', '', 字段):将任意多个值拼接起来,可以是值也可以是字段
可以看到对于concat来说,如果是int,会隐式的转化为string类型
concat_ws:
和concat类似,只不过第一个参数指定的是分隔符,会将所有的字段用指定的分隔符进行拼接。如果分割符为null,那么返回的结果也是null,但是一般不会有人把分隔符指定为null吧。如果字段中有null的话,那么会跳过。
对于concat_ws来说,则需要每一个部分都为string
或者Array<string>
collect_set:
collect_set(col),指定一列,会自动去重返回array
常用函数列转行
假设我们有这样的数据。
move category
疑犯追踪 悬疑,动作,科幻,剧情
lie to me 悬疑,警匪,动作,心理,剧情
战狼2 战争,动作,灾难
现在的需求是把数据变成这样
疑犯追踪 悬疑
疑犯追踪 动作
疑犯追踪 科幻
疑犯追踪 剧情
lie to me 悬疑
lie to me 警匪
lie to me 动作
lie to me 心理
lie to me 剧情
战狼2 战争
战狼2 动作
战狼2 灾难
下面我们就来创建这样的表,然后导入数据
explode:
这个函数用来干啥的呢?我们来看一看就知道了。
可以看到explode
这个函数会自动将数组打开,将元素一个一个的取出来。既然如此,如果想实现我们刚才说的效果,那么再加上movie字段不就行了吗?
但是我们发现它报错了,单独写可以,要是和其他字段组合起来就报错了。但是我们想一想,我们想要的结果是不是类似于一种笛卡尔积的形式啊,movie和category数组的每一个元素进行组合,然后分别作为一列。于是便需要使用另一个函数:
lateral view:
这个中文翻译为侧写
,比如说:小姐姐你的裙子好漂亮啊,把你肚子上的小肉肉都给遮住了。
那么它的语法是什么样子的呢?
select movie, category from movie_info; -- 这种语法是对的,但是不是我们想要的
select movie, explode(category) from movie_info;--是我们想要的,但是语法不对
--怎么办呢?需要这么写
--lateral view explode(category)表示打开后的结果,然后起一个别名category_name
--在select的时候筛选即可,至于那个tem_table是什么?那个是给表起的别名
--这个别名是必须要有的,即便你不用,也要写在那里
select movie, category_name
from
movie_info lateral view explode(category) tem_table as category_name;
窗口函数
什么是窗口函数呢?我举个例子就知道了。比如说,我们有如下数据
name age score
----------------------
mashiro 16 80
satori 16 80
koishi 15 82
nagisa 20 82
kurisu 18 82
tomoyo 19 81
scarlet 400 81
首先对于如上数据,我们根据score聚合显然是可以的
select count(*) from table group by score;
但是如果我在聚合的时候,筛选字段会怎么样呢?
select name,count(*) from table group by score;
这个语法显然是通不过的,因为select后面的字段要么出现在聚合函数里,要么出现在group by 后面,但是name显然没有。其实也好理解,count(*)是根据score统计每一组的个数,最终是每一组都是一条记录,比如这里根据score聚合,那么结果就是2 3 2,总共3条记录,而name还是原来7条,那么此时就对应不上了,所以就会报错,提示select后面的字段必须出现在group by字句中。
这个时候窗口函数的威力就凸显出来了。
我们来创建这样的一张表吧
现在有这样一个需求,获取相同的score出现的总次数以及每一个score
我们先来简化一下,如果是获取每一个score出现的总次数
那么直接select count(*) from student group by score;即可
2
2
3
但是我还想获取每一个score,如果是这样写的话,
select score, count(*) from student group by score;
80 2
81 2
82 3
得到的是这个结果。
但是我们要的是每一个score,也就是我们需要这样的结果
80 2
80 2
81 2
81 2
82 3
82 3
82 3
如果按照之前的写法,按照score聚合之后,score是不会重复的。
但是我们需要是score还是和原来一样
于是我们可以使用窗口函数。
select score, count(*) over() from student;
81 7
81 7
82 7
82 7
82 7
80 7
80 7
但是结果和我们期待的貌似不一样啊,别急且听我慢慢道来。
首先我们这里使用count,但是没有加上group by,这是因为count(*)后面有over
这个过程是将score全部筛选出来,然后count(*) over(),其实over()就是开窗函数,表示开放一个窗口
这个窗口是针对每一条数据集的。
如果over里面没有加上参数,那么默认表示窗口大小是筛选出来的整个数据集。
如果筛选出来的时候使用group by,并且不开窗,那么结果肯定如下。
select score, count(*) from student group by score;
80 2
81 2
82 3
但是在使用group by的时候指定了开窗函数
select score, count(*) over() from student group by score;
可以理解为先对score进行聚合,那么结果如下
80
81
82
然后对象当前筛选出来的数据进行开窗,但是我们没有在over里面指定参数,那么默认的窗口大小是整个数据集。所以结果就是下面这样。
80 3
81 3
82 3
同理:select score, count(*) from student where score=82 group by score;
82 3
只会出现一次
但如果是窗口函数:
select score, count(*) over() from student where score=82;
82 3
82 3
82 3
因为筛选出的82有三条,每一条记录都开窗,窗口大小是整个筛选出来的数据集,所以是上面的结果。
select score, count(*) over() from student where score=82 group by score;
82 1
为什么多指定了group by之后变成了1,因为group by之后只有82,没有窗口函数就是3,因为有3条,但是指定了窗口函数,那么窗口大小是筛选出来的数据集,由于聚合导致记录只有一条,所有再进行count的时候,结果就为1。
有人会觉得你扯了这么多,不还是没有说到重点上面吗?别急,饭要一口一口吃,首先肯定明白了over()的作用,是对查询之后的数据集进行聚合。那么下面我们就可以往over里面加参数了。刚才说了如果over里面不加参数,是对查询出来的整个数据集进行开窗,但是我们可以加上参数限定一下。
回到我们最一开始写的带有开窗函数语句:select score, count(*) over() from student;
现在我们加入参数:
select score, count(*) over(partition by score) from student;
80 2
80 2
81 2
81 2
82 3
82 3
82 3
看到没,这样结果是不是就跟我们期待的一样了呢?再回顾一下,如果只是单纯的group by那么score是不会重复的,就是
80 2
81 2
82 3
表示80出现两次,81出现两次,82出现3次
但是我们需要的是score这一列不能变,如果group by之后80出现两次,那么只要是score=80的,就是2,也就是说,80 2这条记录要重复两次,同理81也是一样,82出现了3次,那么81 3就要重复三次。这就是窗口函数的作用。
当然我们还可以使用其他字段、其他的聚合函数。
select name, avg(score) over(partition by age) from student;
koishi 82.0
satori 80.0
mashiro 80.0
kurisu 82.0
tomoyo 81.0
nagisa 82.0
scarlet 81.0
表示计算出相同年龄的人的平均分,比如koishi 82.0 就表示和koishi年龄相同的所有人的平均分是82.0,当然我这里数据是自己设计的,score比较规整,有兴趣的话可以把score的方差设计的大一些,让score尽量都不一样。
这有点类似于pandas里面group by后面的agg和transform
import pandas as pd
import numpy as np
df = pd.DataFrame({"name": ["koishi", "satori", "mashiro", "kurisu", "tomoyo", "nagisa", "scarlet"],
"age": [15, 16, 16 , 18, 19, 20, 400],
"score": [82, 80, 80, 82, 81, 82, 81]})
print(df.groupby(by=["score"], as_index=False).agg({"name": "count"}).values)
"""
[[80 2]
[81 2]
[82 3]]
"""
print(np.c_[df["score"].values, df.groupby(by=["score"], as_index=False)["name"].transform("count").values])
"""
[[82 3]
[80 2]
[80 2]
[82 3]
[81 2]
[82 3]
[81 2]]
"""
print(np.c_[df["name"].values, df.groupby(by=["age"], as_index=False)["score"].transform("mean").values])
"""
[['koishi' 82.0]
['satori' 80.0]
['mashiro' 80.0]
['kurisu' 82.0]
['tomoyo' 81.0]
['nagisa' 82.0]
['scarlet' 81.0]]
"""
当然开窗函数里面加的是partition by 字段
,表示按照字段分割,每一条都开了一个窗口,但是窗口的大小仅限于开窗字段一样记录,不指定则是全局。那么除了partition by
,还有一个order by
,显然不用解释就是进行排序开窗。
select * from student order by score;
satori 16 80
mashiro 16 80
scarlet 400 81
tomoyo 19 81
kurisu 18 82
nagisa 20 82
koishi 15 82
select name, sum(score) over (order by score) from student;
satori 160
mashiro 160
scarlet 322
tomoyo 322
kurisu 568
nagisa 568
koishi 568
为什么是这个结果,我来解释一下,这个排序开窗是按照顺序从小到大排序,将小于等于自己的作为一个窗口,进行聚合。
最小的是80,小于等于80有两个,所以这两个80开一个窗,sum之后就是160
然后是81,小于等于81的有4个,所以这四个开一个窗
最后是82,所有都小于等于82,因此这个窗口就是全部了。
因此这里就很清晰了,group by分组之后,一组就一条记录,但是开窗函数的话,每一组该是几条还是几条。
那么问题又来了,开窗函数可不可以指定多个呢?显然是可以的,比如说我要根据age分区, 分区之后,再对score进行类和。就可以使用sum(score) over(partition age sort by score),注意这里是sort by,不是order by,sort by表示对相同的分区进行类和
select name, sum(score) over (partition by age order by score) from student;
koishi 82
satori 160
mashiro 160
kurisu 82
tomoyo 81
nagisa 82
scarlet 81
lag(col, n)往前第n行数据:
select name, lag(name, 1, "xxx") over() from student;
scarlet xxx
tomoyo scarlet
kurisu tomoyo
nagisa kurisu
koishi nagisa
satori koishi
mashiro satori
lag(name, 1, "xxx") over()也表示一列,但是当前行的数据是name当前行的上1行,但如果是第一行数据的话,name没有上一行了,所以我们指定了默认值"xxx"
lead(col, n)往后第n行数据:
select name, lead(name, 1, "xxx") over() from student;
scarlet tomoyo
tomoyo kurisu
kurisu nagisa
nagisa koishi
koishi satori
satori mashiro
mashiro xxx
这两个是一对,但是有什么用呢?当我们想跨行操作的时候,就有用了。当然这个也是可以指定分区的。
select name, lead(name, 1, "xxx") over(partition by score) from student;
satori mashiro
mashiro xxx
scarlet tomoyo
tomoyo xxx
kurisu nagisa
nagisa koishi
koishi xxx
表示针对于每一个partition,往下一行
排名函数
排名函数其实也是窗口函数的一种,只是我们把它单独拎出来讲。排名函数有三种
rank():排序相同时会重复,总数不会变。比如:90 90 85 84,那么排名就是1 1 3 4
dense_rank():排序相同时会重复,总数会减少。比如:90 90 85 84,那么排名就是1 1 2 3
row_number():会根据顺序计算,比如:90 90 85 84,那么排名就是1 2 3 4,两个即便相同也是可以比的。比如高考总分一样的话,有的地方没有并列第一,那么就比较数学的分数。
select age, rank() over(order by age desc) as rank1,
dense_rank() over(order by age desc) as rank2,
row_number() over(order by age desc) as rank3
from student order by age desc;
系统函数查看
-
show functions:查看自带了哪些函数
-
desc function 函数:查看某一个函数的用法
-
desc function extended 函数:查看某一个函数的详细用法
自定义函数
我们看到hive内部提供了271个函数,是可以满足绝大部分需求的,但是有时候还是不够,因此hive也支持我们自定义函数。
自定义函数大致分为以下三种:
udf(user defined function):一进一出
udaf(user defined aggregation function):多进一出,类似于聚合函数,count,sum等等
udtf(user defined table-generating function):一进多出,如lateral view explode()
但是这里我就不介绍了,因为我不会java
fetch 抓取& 本地调优
fetch抓取:
fetch抓取是指,hive中对某些情况的查询可以不必使用MapReduce计算,。例如:select * from table;
,在这种情况下,hive可以简单的读取table的存储目录下的文件,然后输出查询结果到控制台。
在hive-default.xml.template中有如下属性:
<property>
<name>hive.fetch.task.conversion</name>
<value>more</value>
</property>
默认是more,老版本minimal,该属性为more 的时候,在全局查找、字段查找、limit查找等、都不走MapReduce。
本地调优:
大多数的hadoop job是需要hadoop提供的完整的可扩展性来处理大数据集的。不过,有时hive的输入数据量是非常小的。在这种情况下,为触发查询执行任务消耗的时间可能会比实际job的执行时间要多得多。对于大多数这种情况,hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间可以明显被缩短。
用户可以通过设置hive.exec.mode.local.auto
的值为true
,来让hive在适当的时候启动这个优化
set hive.exec.mode.local.auto=true //开启本地MapReduce
//设置local MapReduce的最大数据输入量,当输入量小于这个值时采用local MapReduce的方式,默认是134217728,即128M
set hive.exec.mode.local.auto.inputbytes.max=500000000
//设置local MapReduce的最大文件输出个数,当文件输入个小于这个值的时候,采用local MapReduce的方式,默认为4
set hive.exec.mode.local.auto.input.files.max=10