Hive-基础知识-全-
Hive 基础知识(全)
零、前言
随着人们对大数据分析的兴趣与日俱增,Hieve over Hadoop 成为存储、计算和分析大数据的尖端数据解决方案。 类似 SQL 的语法使配置单元更易于学习,并被广泛接受为大数据上交互式 SQL 查询的标准。 配置单元中提供的各种功能为我们提供了在没有高级编码技能的情况下进行复杂大数据分析的能力。 Hive 的成熟让它在 Hadoop 之外的不同计算框架之间逐渐合并和共享其有价值的架构和功能。
Apache Have Essentials,第二版通过介绍大数据领域的背景和概念,以及前两章中设置和熟悉您的配置单元工作环境的过程,为您的大数据之旅做好准备。 在接下来的四章中,本书将指导您使用 Hive 查询语言的示例和技巧来发现和转换大数据背后的价值。 在最后四章中,该书重点介绍了精选的高级主题,如性能、安全性和扩展,作为这一值得进行的大数据之旅的激动人心的冒险之旅。
这本书是写给谁的?
如果您是一名数据分析师、开发人员或用户,希望使用配置单元来研究和分析 Hadoop 中的数据,这本书非常适合您。 无论您是刚接触大数据还是已经是经验丰富的用户,您都可以掌握 Hive 的基本和高级功能。 由于 HQL 与 SQL 非常相似,因此以前使用 SQL 和数据库的一些经验将有助于更好地理解本书。
这本书涵盖了哪些内容
第 1 章,大数据和 Hive 概述,从大数据、Hadoop 生态系统和 Hive 的演变开始。 您还将了解 Hive 架构以及在大数据分析中使用 Hive 的优势。
第 2 章,设置配置单元环境,介绍配置单元环境的设置和配置。 它还包括通过命令行和开发工具使用配置单元。
第 3 章,数据定义和说明概述了配置单元中表、分区、存储桶和视图的基本数据类型和数据定义语言。
第 4 章,数据关联和作用域,向您展示了通过查询、链接和确定配置单元中的数据作用域来发现数据的方法。
第 5 章,数据操作,重点介绍配置单元中数据的交换、移动、排序和转换过程。
第 6 章,数据聚合和采样,解释了使用聚合函数、分析函数、窗口和示例子句进行聚合和采样的方法。
CH 在第 7、性能注意事项之后,介绍了在设计、文件格式、压缩、存储、查询和作业方面的性能注意事项的最佳实践。
第 8 章,可扩展性注意事项描述了通过创建用户定义函数、流、序列化程序和反序列化程序来扩展配置单元的方法。
第 9 章,安全注意事项从身份验证、授权和加密方面介绍了配置单元安全领域。
第 10 章,使用其他工具讨论了配置单元如何与其他大数据工具协同工作。
为了最大限度地利用这本书
如果您对 SQL 有一定的经验,这本书会给您带来最大的好处。 如果您是一名数据分析师、开发人员,或者只是想要快速开始使用 Hive 在 Hadoop 中探索和分析大数据的人,这本书是适合您的。 此外,请在您的系统中安装以下软件。
- JDK 1.8
- Hadoop 2.x.y
- Ubuntu 16.04/CentOS 7
下载示例代码文件
您可以从您的帐户www.Packtpub.com下载本书的示例代码文件。 如果您在其他地方购买了本书,您可以访问www.Packtpub.com/support并注册,让文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
- 登录或注册www.Packtpub.com。
- 选择支持选项卡。
- 单击 Code Downloads&Errata(代码下载和勘误表)。
- 在搜索框中输入图书名称,然后按照屏幕上的说明进行操作。
下载文件后,请确保使用以下最新版本解压缩或解压缩该文件夹:
- WinRar/7-用于 Windows 的 Zip
- 适用于 Mac 的 Zipeg/iZip/UnRarX
- Linux 版 7-Zip/PeaZip
这本书的代码包也托管在 giHub 的https://github.com/PacktPublishing/Apache-Hive-Essentials-Second-Edition上。如果代码有更新,它将在现有的 giHub 存储库中进行更新。
我们还有来自我们丰富的图书和视频目录的其他代码包,请访问https://github.com/PacktPublishing/。 看看他们!
下载彩色图像
我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。 您可以在此处下载:http://www.packtpub.com/sites/default/files/downloads/ApacheHiveEssentialsSecondEdition_ColorImages.pdf。
使用的约定
本书中使用了许多文本约定。
CodeInText
:指示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。 这里有一个例子:“在~/.profile
文件或~/.bashrc
文件中添加必要的系统路径变量”
代码块设置如下:
export HADOOP_HOME=/opt/hadoop
export HADOOP_CONF_DIR=/opt/hadoop/conf
export HIVE_HOME=/opt/hive
export HIVE_CONF_DIR=/opt/hive/conf
export PATH=$PATH:$HIVE_HOME/bin:$HADOOP_HOME/
bin:$HADOOP_HOME/sbin
任何命令行或直线交互输入或输出都写成如下:
$hive
$beeline -u "jdbc:hive2://localhost:10000"
粗体:表示您在屏幕上看到的新术语、重要单词或单词。 例如,菜单或对话框中的单词显示在文本中,如下所示。 下面是一个示例:“从界面中选择首选项。”
Warnings or important notes appear like this. Tips and tricks appear like this.
保持联系
欢迎读者的反馈。
一般反馈:发送电子邮件feedback@packtpub.com
,并在邮件主题中提及书名。 如果您对本书的任何方面有任何疑问,请给我们发电子邮件至questions@packtpub.com
。
勘误表:虽然我们已经竭尽全力确保内容的准确性,但错误还是会发生。 如果您在这本书中发现了错误,请向我们报告,我们将不胜感激。 请访问www.Packtpub.com/Submit-errata,选择您的图书,单击勘误表提交表链接,然后输入详细信息。
盗版:如果您在互联网上遇到任何形式的非法复制我们的作品,请您提供地址或网站名称,我们将不胜感激。 请拨打copyright@packtpub.com
与我们联系,并提供该材料的链接。
如果您有兴趣成为一名作者:如果有一个您擅长的主题,并且您有兴趣撰写或投稿一本书,请访问Auths.Packtpub.com。
评论
请留下评论。 一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢? 这样,潜在读者就可以看到并使用您不偏不倚的意见来做出购买决定,我们 Packt 可以了解您对我们产品的看法,我们的作者也可以看到您对他们的书的反馈。 谢谢!
有关 Packt 的更多信息,请访问Packtpub.com。
一、大数据和 Hive 概述
本章概述了大数据和 Hive,尤其是 Hadoop 生态系统中的大数据和 Hive。 它简要介绍了大数据的演变,让读者知道自己在大数据之旅中处于什么位置,并可以在未来的学习中找到自己喜欢的领域。 本章还介绍了 Hive 如何成为大数据生态系统中的领先工具之一,以及为什么它仍然具有竞争力。
在本章中,我们将介绍以下主题:
- 从数据库、数据仓库到大数据的短暂历史
- 引入大数据
- 关系型和非 SQL 数据库与 Hadoop
- 批处理、实时处理和流处理
- Hadoop 生态系统概述
- Hive 概述
简短的历史
20 世纪 60 年代,当计算机成为企业更具成本效益的选择时,人们开始使用数据库来管理数据。 后来,在 20 世纪 70 年代,关系数据库变得更加流行以满足业务需求,因为它们可以轻松而紧密地将物理数据与逻辑业务联系起来。 在接下来的十年里,结构化查询语言(SQL)成为数据库的标准查询语言。 SQL 的有效性和简单性促使许多人使用数据库,并使数据库更接近广大用户和开发人员。 不久,人们观察到人们使用数据库进行数据应用和管理,并持续了很长一段时间。
一旦收集到大量的数据,人们就开始思考如何处理这些历史数据。 然后,术语数据仓库在 20 世纪 90 年代出现。 从那时起,人们开始讨论如何通过回顾历史数据来评估当前的业绩。 创建了各种数据模型和工具,以帮助企业有效地管理、转换和分析其历史数据。 传统的关系数据库也在发展,以提供更高级的聚合和分析功能以及数据仓库的优化。 主要的查询语言仍然是 SQL,但与以前的版本相比,它更加直观和强大。 数据仍然是结构良好的,模型是标准化的。 随着我们进入 21 世纪,互联网逐渐成为创造大多数数据的种类和数量最多的行业。 社交媒体分析、网络挖掘和数据可视化等新技术帮助许多企业和公司处理海量数据,以便更好地了解其客户、产品、竞争和市场。 数据量的增长和数据格式的变化比以往任何时候都快,这迫使人们寻找新的解决方案,特别是在研究和开源领域。 于是,大数据成为众多研究人员和企业关注的热门话题和具有挑战性的领域。
然而,每一次挑战都蕴藏着巨大的机遇。 在 2010 年代,Hadoop 作为大数据开源项目之一,由于其开源许可、活跃的社区和处理海量数据的能力而开始受到广泛关注。 这是少数几次开放源码项目在任何商业软件产品出现之前导致技术趋势改变的一次。 不久之后,NoSQL 数据库、实时分析和机器学习作为追随者,迅速成为 Hadoop 大数据生态系统的重要组件。 有了这些大数据技术,公司能够回顾过去,评估当前,并抓住未来的机遇。
引入大数据
大数据不仅仅是海量的数据。 这里,单词大指的是大范围的数据。 这个领域的一个众所周知的说法是,借助以字母 V 开头的三个单词来描述大数据:体积、速度和多样性。 但分析和数据科学界已经看到,除了大数据的基本三个 V 之外,数据在其他维度上也不同,如准确性、可变性、波动性、可视化和价值。 到目前为止提到的不同的 V 的解释如下:
- 卷:指以秒为单位生成的数据量。 当今世界 90%的数据是在过去两年中创建的。 从那时起,世界上的数据每两年翻一番。 如此大量的数据主要由机器、网络、社交媒体和传感器生成,包括结构化、半结构化和非结构化数据。
- 速度:这是指数据生成、存储、分析和移动的速度。 随着联网设备的出现,无线或有线机器和传感器可以在创建数据后立即传递数据。 这导致了实时数据流,并帮助企业做出有价值的快速决策。
- Varity:指不同的数据格式。 过去,数据以
.txt
、.csv
和.dat
格式从文件系统、电子表格和数据库等数据源中存储。 这种类型的数据驻留在记录或文件中的固定字段中,称为结构化数据。 如今,数据并不总是采用传统的结构化格式。 更新的半结构化或非结构化形式的数据也是通过各种方法生成的,比如电子邮件、照片、音频、视频、PDF、SMS,甚至是我们不知道的东西。 这些不同的数据格式给存储和分析数据带来了问题。 这是我们在大数据领域需要克服的主要挑战之一。 - 准确性:指数据的质量,如数据的可信性、偏差、噪声、异常等。 数据损坏是很正常的。 它可能是由于多种原因造成的,例如打字错误、缺少或不常见的缩写、数据重新处理和系统故障。 然而,忽视这些恶意数据可能会导致不准确的数据分析,并最终导致错误的决策。 因此,确保数据在数据审核和修正方面是正确的,对于大数据分析来说是非常重要的。
- 可变性:指数据的变化。 这意味着相同的数据在不同的上下文中可能有不同的含义。 在进行情绪分析时,这一点尤为重要。 分析算法能够理解上下文,并发现该上下文中数据的确切含义和价值。
- 波动性:指数据的有效期和存储时间。 这对于实时分析尤其重要。 它需要确定数据的目标时间窗口,以便分析师可以专注于特定的问题,并从分析中获得良好的性能。
- 可视化:这指的是让数据更容易理解的方式。 可视化不仅仅意味着普通的图表或饼图;它还使大量数据在易于理解的多维视图中变得可理解。 可视化是显示数据更改的一种创新方式。 它需要大数据分析师和业务领域专家之间的大量交互、对话和共同努力,才能使可视化变得有意义。
- 值:指对大数据进行数据分析所获得的知识。 大数据的价值在于组织如何将自己转变为大数据驱动型公司,并将大数据分析的洞察力用于决策。
总而言之,大数据不仅仅是大量数据,它是从现有数据中发现新见解并指导分析新数据的一种实践。 大数据驱动的企业将更加敏捷,更具竞争力,以克服挑战,赢得竞争。
关系数据库和 NoSQL 数据库与 Hadoop
为了更好地理解关系数据库、NoSQL 数据库和 Hadoop 之间的区别,让我们将它们与旅行方式进行比较。 你会惊讶地发现它们有很多相似之处。 当人们旅行时,他们要么乘坐汽车,要么乘坐飞机,这取决于旅行的距离和费用。 例如,当你从多伦多到温哥华旅行时,从旅行时间和成本来看,飞机总是第一选择。 当你从多伦多去尼亚加拉瀑布旅行时,汽车总是不错的选择。 当你从多伦多到蒙特利尔旅行时,有些人可能更喜欢开车而不是飞机。 这里的距离和成本就像大数据量和投资一样。 传统的关系数据库就像汽车,Hadoop 大数据工具就像飞机。 当您处理少量数据(短距离)时,关系数据库(如汽车)始终是最佳选择,因为它处理少量或中等数量的数据既快又灵活。 当您处理大量数据(远距离)时,Hadoop(就像飞机一样)是最佳选择,因为它在处理海量数据时更具线性可伸缩性、速度和稳定性。 你可以从多伦多开车到温哥华,但太花时间了。 你也可以从多伦多乘飞机去尼亚加拉瀑布,但去机场的路上要花更多的时间,而且比开车更贵。 另外,你可以坐船或火车。 这类似于 NoSQL 数据库,它提供了关系数据库和 Hadoop 的特性和平衡,具有良好的性能和对中等到大量数据的多种数据格式支持。
批处理、实时处理和流处理
批处理用于批量处理数据。 它从输入读取数据,对其进行处理,然后将其写入输出。 Apache Hadoop 是使用 MapReduce 范例的分布式批处理系统最知名和最流行的开源实现。 数据存储在名为Hadoop Distributed File System(HDFS)的共享和分布式文件系统中,并划分为拆分,拆分是 MapReduce 处理的逻辑数据分区。
要使用 MapReduce 范例处理这些拆分,映射任务将读取拆分并将其所有键/值对传递给映射函数,然后将结果写入中间文件。 映射阶段完成后,还原器读取通过无序排列过程发送的中间文件,并将它们传递给 Reduce 函数。 最后,Reduce 任务将结果写入最终输出文件。 MapReduce 模型的优点包括使分布式编程更容易、近线性加速、良好的可扩展性以及容错能力。 这种批处理模型的缺点是不能执行递归或迭代作业。 此外,明显的批处理行为是,在 Reduce 作业开始之前,所有输入都必须由 MAP 准备好,这使得 MapReduce 不适合在线和流处理用例。
实时处理用于处理数据并几乎立即得到结果。 在大数据的实时ad hoc
查询领域,这一概念最先由 Google 在 Dremel 实现。 它采用一种新颖的列存储格式用于嵌套结构,并使用快速索引和可伸缩聚集算法并行计算查询结果,而不是批处理序列。 这两种技术是实时处理的主要特点,并被类似的实现所使用,例如 Impala(https://impala.apache.org/)、PRESTO(https://prestodb.io/)和 Drill(https://drill.apache.org/),它们由列存储和数据格式,例如 PARQUE(https://parquet.apache.org/)提供支持。 Orc(https://orc.apache.org/)、CarbonData(https://carbondata.apache.org/)和箭头(https://arrow.apache.org/)。 另一方面,内存计算无疑为实时处理提供了更快的解决方案。 内存计算提供了非常高的带宽,超过 10 千兆字节/秒,而硬盘只有 200 兆字节/秒。 此外,与硬盘相比,延迟相对较低,纳秒和毫秒。 随着内存的价格越来越低,内存计算作为一种实时解决方案变得更加实惠,比如 Apache Spark(https://spark.apache.org/),它是内存计算的一种流行的开源实现。 Spark 可以很容易地与 Hadoop 集成,其内存中的数据结构弹性分布式数据集(RDD)可以从 HDFS 和 HBase 等数据源生成,以实现高效缓存。
流处理用于连续处理并作用于实况流数据以获得结果。 在流处理中,有两个常用的通用流处理框架:Storm(https://storm.apache.org/)和 Flink(https://flink.apache.org/)。 这两个框架都运行在Java 虚拟机(JVM)上,并且都处理键控流。 就编程模型而言,Storm 为您提供了构建框架的基本工具,而 Flink 为您提供了定义良好且易于使用的框架。 此外,Samza(http://samza.apache.org/)和 Kafka Stream(https://kafka.apache.org/documentation/streams/)利用 Kafka 进行消息缓存和转换。 最近,Spark 还在其创新的连续处理模式方面提供了一种流处理。
Hadoop 生态系统概述
Hadoop 于 2011 年由 Apache 首次发布,版本为 1.0.0,仅包含 HDFS 和 MapReduce。 Hadoop 从一开始就被设计为计算(MapReduce)和存储(HDFS)平台。 随着大数据分析需求的增加,Hadoop 吸引了很多其他软件来解决大数据问题,并融入了以 Hadoop 为中心的大数据生态系统。 下图简要概述了 Apache 堆栈中的 Hadoop 大数据生态系统:
Apache Hadoop ecosystem
在当前的 Hadoop 生态系统中,HDFS仍然是使用硬盘存储的主要选择,AlLuxio提供了虚拟分布式内存替代方案。 在 HDFS 之上,可以使用 Parquet、Avro 和 ORC 数据格式以及更快的压缩算法来进行计算和存储优化。 Yarn作为第一个 Hadoop 通用资源管理器,专为更好的资源管理和伸缩性而设计,Spark和Ignite作为内存计算引擎,也可以在 Yarn 上运行,与 Hadoop 紧密合作。
另一方面,Kafka、Flink和Storm正在主导流处理。HBase是领先的 NoSQL 数据库,特别是在 Hadoop 群集上。对于机器学习,它包括Spark MLlib和Madlib以及新的Mahout。 Flume是一个成熟的、分布式的、可靠的日志收集工具,用于将数据移动或收集到 HDFS。 Impala和Drill能够直接针对 Hadoop 上的数据启动交互式 SQL 查询。 此外,通过Spark/tez以及Live Long and Process(LLAP),用户可以通过内存数据缓存在不同的计算框架(而不是 MapReduce)上的长时间进程中运行查询。 因此,Hive在生态系统中扮演着比以往任何时候都更重要的角色。 我们还很高兴地看到,Ambari作为新一代集群管理工具,除了ZooKeeper之外,还提供了更强大的集群管理和协调功能。 对于日程安排和工作流管理,我们可以使用气流或Oozie。 最后,我们有一个开源治理和元数据服务出现了,Altas,它支持生态系统中大数据的严格合规性和谱系。
Hive 概述
HIVE 是 Hadoop 中针对 PB 级数据的 SQL 查询的标准。 它提供对 HDFS 中数据的 SQL 式访问,使 Hadoop 能够用作数据仓库。 配置单元查询语言(HQL)与关系数据库中的标准 SQL 具有相似的语义和功能,因此经验丰富的数据库分析人员可以轻松掌握它。 HIVE 的查询语言可以在不同的计算引擎上运行,比如 MapReduce、TEZ 和 Spark。
HIVE 的元数据结构在 HDFS 之上提供了一个高级的表状结构。 它支持三种主要数据结构:表、分区和存储桶。 这些表对应于 HDFS 目录,可以划分为分区,其中数据文件可以划分为存储桶。 配置单元的元数据结构通常是 Hadoop 上的读取时架构概念的架构,这意味着在将数据存储到 HDFS 之前,您不必在配置单元中定义架构。 在存储数据后应用配置单元元数据可为您的数据工作带来更大的灵活性和效率。 Hive 元数据的流行使其成为事实上的描述大数据的方式,并被大数据生态系统中的许多工具使用。
下图是 Hadoop 生态系统中配置单元的架构视图。 配置单元元数据存储(也称为元存储)可以使用嵌入式、本地或远程数据库。 节俭服务器是基于 Apache Thrift 服务器技术构建的。 在其最新版本 2 中,hiveserver2 能够处理多个并发客户端,支持 Kerberos、LDAP 和自定义的可插拔身份验证,并为 JDBC 和 ODBC 客户端提供更好的选项,尤其是元数据访问。
Hive architecture
以下是我们可以牢记的 Hive 的一些亮点:
- 与 MapReduce 相比,HIVE 提供了一个简单且优化的查询模型,编码更少
- HQL 和 SQL 具有相似的语法
- 在相同数量的大数据集上,HIVE 的查询响应时间通常比其他设备快得多
- HIVE 支持在不同的计算框架上运行
- HIVE 支持
ad hoc
在 HDFS 和 HBase 上查询数据 - 配置单元支持用户定义的
java
/scala
函数、脚本和过程语言,以扩展其功能 - 成熟的 JDBC 和 ODBC 驱动程序允许许多应用提取配置单元数据以实现无缝报告
- HIVE 允许用户使用 SerDe 和输入/输出格式读取任意格式的数据
- HIVE 是一个稳定可靠的批处理工具,可以长期投入生产
- HIVE 具有定义良好的体系结构,用于元数据管理、身份验证和查询优化
- 有一个庞大的实践者和开发人员社区,他们正在工作和使用配置单元
简略的 / 概括的 / 简易判罪的 / 简易的
读完本章后,我们现在能够理解何时以及为什么要使用大数据而不是传统的关系数据库。 我们还了解了批处理、实时处理和流处理之间的区别。 我们现在熟悉 Hadoop 生态系统,尤其是 Hive。 我们回到了过去,回顾了数据库、数据仓库和大数据的历史。 我们还探讨了一些大数据术语、Hadoop 生态系统、Hive 架构以及使用 Hive 的优势。
在下一章中,我们将练习安装配置单元,并回顾在命令行环境中开始使用配置单元所需的所有工具。
二、设置 Hive 环境
本章将介绍如何在集群和云中安装和设置 Hive 环境。 它还介绍了基本配置单元命令和配置单元集成开发环境的用法。
在本章中,我们将介绍以下主题:
- 从 Apache 安装配置单元
- 安装供应商提供的配置单元
- 在云中使用 Hive
- 使用配置单元命令
- 使用配置单元 IDE
从 Apache 安装配置单元
为了介绍配置单元的安装,我们将使用配置单元版本 2.3.3 作为示例。 此安装的安装前要求如下:
- JDK 1.8
- Hadoop 2.x.y
- Ubuntu 16.04/CentOS 7
Since we focus on Hive in this book, the installation steps for Java and Hadoop are not provided here. For steps on installing them, please refer to https://www.java.com/en/download/help/download_options.xml and http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/ClusterSetup.html.
以下步骤介绍如何在命令行环境中安装 Apache Have:
- 从 Apache 配置单元下载配置单元并解压缩:
$cd /opt
$wget https://archive.apache.org/dist/hive/hive-2.3.3/apache-
hive-2.3.3-bin.tar.gz
$tar -zxvf apache-hive-2.3.3-bin.tar.gz
$ln -sfn /opt/apache-hive-2.3.3 /opt/hive
- 在文件
~/.profile
或文件~/.bashrc
中添加必要的系统路径变量:
export HADOOP_HOME=/opt/hadoop
export HADOOP_CONF_DIR=/opt/hadoop/conf
export HIVE_HOME=/opt/hive
export HIVE_CONF_DIR=/opt/hive/conf
export PATH=$PATH:$HIVE_HOME/bin:$HADOOP_HOME/
bin:$HADOOP_HOME/sbin
- 立即启用设置:
$source ~/.profile
- 创建配置文件:
$cd /opt/hive/conf
$cp hive-default.xml.template hive-site.xml
$cp hive-exec-log4j.properties.template hive-exec-
log4j.properties
$cp hive-log4j.properties.template hive-log4j.properties
- 修改
$HIVE_HOME/conf/hive-site.xml
,,其中有一些重要参数需要设置:
hive.metastore.warehourse.dir
:这是到 Hive 仓库位置的路径。 默认情况下,它位于/user/hive/warehouse
。hive.exec.scratchdir
:这是临时数据文件位置。 默认情况下,它位于/tmp/hive-${user.name}
。
默认情况下,配置单元使用 Derby(http://db.apache.org/derby/)数据库作为元数据存储。 它还可以使用其他关系数据库(如 Oracle、PostgreSQL 或 MySQL)作为元存储。 要在其他数据库上配置元存储区,应在hive-site.xml
中配置以下参数:
javax.jdo.option.ConnectionURL
:这是 JDBC URL 数据库javax.jdo.option.ConnectionDriverName
:这是 JDBC 驱动程序类名javax.jdo.option.ConnectionUserName
:这是用于访问数据库的用户名javax.jdo.option.ConnectionPassword
:和这是用于访问数据库的密码
以下是使用 MySQL 作为metastore
数据库的示例设置:
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://localhost/metastore?createDatabaseIfNotExist=true
</value>
<description>JDBC connect string for a JDBC metastore</description>
</property>
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
<description>Driver class name for a JDBC metastore</description>
</property>
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>hive</value>
<description>username to use against metastore database</description>
</property>
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>mypassword</value>
<description>password to use against metastore database</description>
</property>
<property>
<name>hive.metastore.uris</name>
<value>thrift://localhost:9083</value>
<description>By specify this we do not use local mode of metastore</description>
</property>
- 确保 MySQL JDBC 驱动程序在以下位置可用:
$HIVE_HOME/lib
:
$ln -sfn /usr/share/java/mysql-connector-java.jar
/opt/hive/lib/mysql-connector-java.jar
The difference between using default Derby or configured relational databases as the metastore is that the configured relational database offers a shared service so that all hive users can see the same metadata set. However, the default metastore setting creates the metastore under the folder of the current user, so it is only visible to this user. In the real production environment, it always configures an external relational database as the Hive metastore.
- 在数据库中创建具有适当权限的配置单元
metastore
表,并使用schematool
初始化模式:
$mysql -u root --password="mypassword" -f \
-e "DROP DATABASE IF EXISTS metastore; CREATE DATABASE IF NOT
EXISTS metastore;"
$mysql -u root --password="mypassword" \
-e "GRANT ALL PRIVILEGES ON metastore.* TO 'hive'@'localhost'
IDENTIFIED BY 'mypassword'; FLUSH PRIVILEGES;"
$schematool -dbType mysql -initSchema
- 由于配置单元在 Hadoop 上运行,请先启动 HDFS 和 Yarn 服务,然后启动
metastore
和hiveserver2
服务:
$start-dfs.sh
$start-yarn.sh
$hive --service metastore 1>> /tmp/meta.log 2>> /tmp/meta.log &
$hive --service hiveserver2 1>> /tmp/hs2.log 2>> /tmp/hs2.log &
- 使用
hive
或beeline
命令连接配置单元,以验证安装是否成功:
$hive
$beeline -u "jdbc:hive2://localhost:10000"
安装供应商提供的配置单元
目前,Cloudera 和 Hortonworks 等许多公司已经将 Hadoop 生态系统和管理工具打包到一个易于管理的企业发行版中。 每家公司采取的策略略有不同,但所有这些包的共识是让 Hadoop 生态系统更容易、更稳定地供企业使用。 例如,我们可以使用供应商分发中打包的 Cloudera Manager(https://www.cloudera.com/products/product-components/cloudera-manager.html)或 Ambari(https://ambari.apache.org/)等 Hadoop 管理工具轻松安装配置单元。 安装并启动管理工具后,我们可以通过以下步骤将配置单元服务添加到 Hadoop 群集:
-
登录 Cloudera Manager/Ambari,然后单击添加服务选项进入添加服务向导
-
选择要安装的服务,例如配置单元
-
为
hiveserver2
、metastore server
、WebHCat server
等选择适当的主机 -
配置所有
metastore server
个数据库连接以及其他必要的配置 -
检查并确认安装
For practice, we can import the vendors quick-start sandbox (https://hortonworks.com/products/sandbox/ or https://www.cloudera.com/downloads/quickstart_vms.html), which has commonly-used Hadoop ecosystem tools pre-installed. In addition, an automatic and optimized Hadoop environment provision virtual machine is also available (https://github.com/datafibers/lab_env) to install on computers with less RAM.
在云中使用 Hive
目前,所有主要的云服务提供商,如亚马逊、微软和谷歌,都提供成熟的 Hadoop 和 Have 作为云服务。 使用云端版本的 Hive 非常方便。 它几乎不需要安装和设置。 Amazon EMR(Hadoop)是云中最早的 http://aws.amazon.com/elasticmapreduce/服务。 但是,它不是一个纯粹的开源版本,因为它被定制为仅在Amazon Web Services(AWS)上运行。 Hadoop 企业服务和分发提供商(如 Cloudera 和 Hortonworks)也提供了工具,可以在不同的公共云或私有云上轻松部署自己的分发版本。 Cloudera Director(Hadoop)和 CloudBreak(http://www.cloudera.com/content/cloudera/en/products-and-services/director.html)通过简单的自助式界面开放云中的 Hadoop 部署,并且完全受 AWS、Windows Azure、Google Cloud Platform 和 OpenStack 的支持。 虽然 Hadoop 最初是在 Linux 上构建的,但 Hortonworks 和微软已经合作成功地将 Hadoop 引入了基于 Windows 的平台和云。 这里所有 Hadoop 云服务提供商的共识是,允许企业以更少的工作量和更少的成本提供高度可用、灵活、高度安全、易于管理和可管理的 Hadoop 群集。
使用配置单元命令
Hive 最先以hiveserver1
开头。 但是,此版本的配置单元服务器不太稳定。 它有时会悄悄地暂停或阻止客户端的连接。 从 v0.11.0 开始,配置单元包含了一个名为hivesever2
的新储蓄服务器来取代hiveserver1
。 hiveserver2
具有专为多客户端并发和改进身份验证而设计的增强型服务器。 它还建议使用beeline
作为主要配置单元命令行界面,而不是使用hive
命令。 两个版本的服务器之间的主要区别在于客户端连接到它们的方式。 hive
是基于 Apache-Thrift 的客户端,beeline
是 JDBC 客户端。 hive
命令直接连接到配置单元驱动程序,因此我们需要在客户机上安装配置单元库。 但是,beeline
通过 JDBC 连接连接到hiveserver2
,而不在客户端安装配置单元库。 这意味着我们可以从群集外部远程运行beeline
。 有关hiveserver2
及其接口访问的更多用法,请参考https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients。
以下两个表格列出了考虑不同用户首选项的不同命令模式下的常用命令:
目的 | **hiveserver2 - beeline** |
**hiveserver1 - hive** |
---|---|---|
连接服务器 | beeline –u <jdbc_url> |
hive -h <hostname> -p <port> |
帮助 / 有益于 / 促进 | beeline -h |
hive -H |
运行查询 | beeline -e "hql query" |
|
beeline -f hql_query_file.hql |
||
beeline -i hql_init_file.hql |
hive -e "hql query" |
|
hive -f hql_query_file.hql |
||
hive -i hql_init_file.hql |
||
赛斯(男子名)易变的 / 可变的 / 方向不定的 / 变量的 | beeline --hivevar var_name=var_value |
hive --hivevar var_name=var_value |
目的 | **hiveserver2** **- beeline** |
**hiveserver1** **- hive** |
---|---|---|
进入模式 | beeline |
hive |
连接服务器 | !connect <jdbc_url> |
不适用不适用 |
列出表格 | !table |
|
show tables; --also support |
show tables; |
|
列表列 | !column table_name |
|
desc table_name; |
desc table_name; |
|
运行查询 | select * from table_name; |
select * from table_name; |
保存结果 | !record result_file.dat |
|
!record |
不适用不适用 | |
运行 shell cmd | !sh ls |
!ls; |
运行 DFS CMD | dfs -ls; |
dfs -ls; |
运行 hql 文件 | !run hql_query_file.hql |
source hql_query_file.hql; |
退出模式 | !quit |
quit; |
In addition, Hive configuration settings and properties can be accessed and overwritten by the SET
keyword in the interactive mode. For more details, refer to the Apache Hive wiki at https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties.
For beeline, ;
is not needed after the command that starts with !
. Both commands do not support running a pasted query with <tab>
inside, because <tab>
is used for auto-complete by default in the environment. Alternatively, running the query from files has no such issues. In interactive mode, we can use the keyboard's up and down arrow keys to retrieve the previous commands. The !history
command can be used in beeline to show the command's history. In addition, the dfs
command may be disabled in beeline for permissions control in some Hadoop distributions. Both commands support variable substitution, which refers to https://cwiki.apache.org/confluence/display/Hive/LanguageManual+VariableSubstitution.
使用配置单元 IDE
除了命令行界面,还有其他集成开发环境(IDE)工具可用于支持配置单元。 其中最好的是Oracle SQL Developer,它利用了 Oracle IDE 的强大功能,并且完全免费使用。 由于 Oracle SQL Developer 支持常规 JDBC 连接,因此在同一 IDE 中在配置单元和其他支持 JDBC 的数据库之间切换非常方便。 Oracle SQL Developer 从 4.0.3 版开始支持配置单元。 将其配置为与配置单元配合使用非常简单:
- 下载 Oracle SQL developer(http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html)。
- 下载配置单元 JDBC 驱动程序(https://www.cloudera.com/downloads/connectors/hive/jdbc.html)。
- 将驱动程序文件解压缩到本地目录。
- 启动 Oracle SQL Developer,然后导航到首选项、|数据库、|第三方 JDBC 驱动程序。
- 将解压目录中包含的所有
JAR
文件添加到窗口,如以下截图所示:
- 单击[确定和按钮,然后重新启动 Oracle SQL Developer。
- 在配置单元和选项卡中创建新连接,给出正确的连接名称、用户名、密码、主机名和(
hiveserver2 hostname
)、端口和数据库。 然后,点击[添加、和、连接和按钮连接到配置单元:
在 Oracle SQL Developer 中,我们可以运行所有配置单元交互命令和 HQL 查询。 我们还可以利用该工具的向导浏览或导出配置单元表格中的数据。 除了 Oracle SQL Developer,其他数据库 IDE,如 DBVisualizer(https://www.dbvis.com/)或 Quirrel SQL Client(http://squirrel-sql.sourceforge.net/)也可以使用 ODBC 或 JDBC 驱动程序连接到配置单元。 然而,它的功能并不强大,很少使用。 相反,Ambari Hive 视图和色调(http://gethue.com/)都是 Hadoop 和 Hive 生态系统的流行、用户友好且功能强大的 Web IDE。 在第 10 章、**使用其他工具中有更多有关使用这些 IDE 的详细信息。
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,我们学习了如何在不同的环境中设置 Hive。 我们还研究了在beeline
和hive
的命令行和交互模式中使用配置单元命令的几个示例。 由于在配置单元中使用 IDE 非常高效,因此我们介绍了 Oracle SQL Developer for Have 的安装过程。 现在您已经完成了本章,您应该能够在本地设置您自己的配置单元环境并使用配置单元。
在下一章中,我们将深入研究 Hive 的数据定义语言的细节。
三、数据定义和描述
本章介绍了配置单元中描述数据的基本数据类型、数据定义语言和模式。 它还介绍了通过使用内部或外部表、分区、存储桶和视图正确有效地描述数据的最佳实践。 在本章中,我们将介绍以下主题:
- 了解数据类型
- 数据类型转换
- 数据定义语言
- 数据库
- 桌子 / 列表 / 表格 / 平面
- 隔断
- 桶 / 一桶的量 / 戽斗 / 铲斗
- 观点
了解数据类型
配置单元数据类型分为两种类型:原始类型和复杂类型。 String
和Int
是最有用的原语类型,大多数 HQL 函数都支持它们。 基元类型的详细信息如下:
可以包含一组任何类型的字段。 复杂类型允许嵌套类型。 复杂类型的详细信息为
基元类型 | 说明 | 示例 |
---|---|---|
TINYINT |
它有 1 个字节,从-128 到127 。 后缀是Y 。 它被用作一个小范围的数字。 |
10Y |
SMALLINT |
它有 2 个字节,来自-32,768 to 32,767 。 后缀是S 。 它用作常规的描述性数字。 |
10S |
INT |
它有 4 个字节,来自-2,147,483,648 to 2,147,483,647 。 |
10 |
BIGINT |
它有 8 个字节,从-9,223,372,036,854,775,808 到9,223,372,036,854,775,807 。 后缀是L 。 |
100L |
FLOAT |
这是一个从1.40129846432481707e<sup>-45</sup> 到3.40282346638528860e<sup>+38</sup> (正数或负数)的 4 字节单精度浮点数。 目前还不支持科学记数法。 它存储非常接近的数值近似值。 |
1.2345679 |
DOUBLE |
这是一个从4.94065645841246544e<sup>-324d</sup> 到1.79769313486231570e<sup>+308d</sup> (正数或负数)的 8 字节双精度浮点数。 目前还不支持科学记数法。 它存储非常接近的数值近似值。 |
1.2345678901234567 |
BINARY |
这是在配置单元 0.8.0 中引入的,仅支持CAST 到STRING ,反之亦然。 |
1011 |
BOOLEAN |
这是TRUE 或FALSE 值。 |
TRUE |
STRING |
这包括用单引号(' )或双引号(" )表示的字符。 配置单元在字符串中使用 C 样式转义。 最大尺寸约为 2G。 |
'Books' 或"Books" |
CHAR |
从配置单元 0.13.0 开始提供此功能。 在配置单元 0.14.0 之后,大多数 UDF 将适用于此类型。 最大长度固定为255 。 |
'US' 或"US" |
VARCHAR |
从配置单元 0.12.0 开始提供此功能。 在配置单元 0.14.0 之后,大多数 UDF 将适用于此类型。 最大长度固定为65,355 。 如果转换/分配给varchar 值的字符串值超过指定的长度,该字符串将被自动截断。 |
'Books' 或"Books" |
DATE |
它以YYYY-MM-DD 的格式描述特定的年、月和日。 从配置单元 0.12.0 开始提供。 日期范围是从0000-01-01 到9999-12-31 。 |
2013-01-01 |
TIMESTAMP |
它以YYYY-MM-DD HH:MM:SS[.fff...] 的格式描述特定的年、月、日、小时、分钟、秒和毫秒。 从配置单元 0.8.0 开始提供。 |
2013-01-01 12:00:01.345 |
配置单元有三种主要的复杂类型:ARRAY
、MAP
和STRUCT
。 这些数据类型构建在原始数据类型之上。 ARRAY
和MAP
与 Java 中的类似。 STRUCT
是一种记录类型,可以包含一组任何类型的字段。 复杂类型允许嵌套类型。 复杂类型的详细信息如下:
复杂类型 | 说明 | 示例 |
---|---|---|
ARRAY |
这是相同类型的项目列表,例如[val1 、val2 等]。 您可以使用array_name[index] 访问该值,例如,fruit[0]="apple" 。 索引从 0 开始。 |
"apple","orange","mango" |
MAP |
这是一组键-值对,例如{key1, val1, key2, val2, and so on} 。 您可以使用map_name[key] 访问该值,例如,fruit[1]="apple" 。 |
{1: "apple",2: "orange"} |
STRUCT |
这是任何类型字段的用户定义结构,例如{val1、val2、val3 等}。 默认情况下,STRUCT 和字段名称将为 col1、col2,依此类推。 您可以使用structs_name.column_name 访问该值,例如,fruit.col1=1 。 |
{1, "apple"} |
NAMED STRUCT |
这是用户定义的任意数量的类型化字段的结构,例如{name1, val1, name2, val2, and so on} 。 您可以使用structs_name.column_name 访问该值,例如,fruit.apple="gala" 。 |
{"apple":"gala","weight kg":1} |
UNION |
这是一个恰好具有任意一种指定数据类型的结构。 从配置单元 0.7.0 开始提供。 它并不常用。 | {2:["apple","orange"]} |
For MAP
, the type of keys and values are unified. However, STRUCT
is more flexible. STRUCT
is more like a table, whereas MAP
is more like an ARRAY
with a customized index.
以下是所有常用数据类型的简短练习。 CREATE
、LOAD
和SELECT
语句的详细信息将在后面的章节中介绍。 让我们来看看这个练习:
- *数据准备如下:
$vi employee.txt
Michael|Montreal,Toronto|Male,30|DB:80|Product:Developer^DLead
Will|Montreal|Male,35|Perl:85|Product:Lead,Test:Lead
Shelley|New York|Female,27|Python:80|Test:Lead,COE:Architect
Lucy|Vancouver|Female,57|Sales:89,HR:94|Sales:Lead
- 使用 JDBC:URL 登录至直线:
$beeline -u "jdbc:hive2://localhost:10000/default"
- 使用各种数据类型创建表格(
>
表示直线交互模式):
> CREATE TABLE employee (
> name STRING,
> work_place ARRAY<STRING>,
> gender_age STRUCT<gender:STRING,age:INT>,
> skills_score MAP<STRING,INT>,
> depart_title MAP<STRING,ARRAY<STRING>>
> )
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|'
> COLLECTION ITEMS TERMINATED BY ','
> MAP KEYS TERMINATED BY ':'
> STORED AS TEXTFILE;
No rows affected (0.149 seconds)
- 验证表是否已创建:
> !table employee
+---------+------------+------------+--------------+---------+
|TABLE_CAT|TABLE_SCHEMA| TABLE_NAME | TABLE_TYPE | REMARKS |
+---------+------------+------------+--------------+---------+
| |default | employee | MANAGED_TABLE| |
+---------+------------+------------+--------------+---------+
> !column employee
-------------+-------------+-------------+-------------------+
| TABLE_SCHEM | TABLE_NAME | COLUMN_NAME | TYPE_NAME |
+-------------+-------------+-------------+------------------+
| default | employee | name | STRING |
| default | employee | work_place | array<string> |
| default | employee | gender_age |
struct<gender:string,age:int>|
| default | employee | skills_score| map<string,int> |
| default | employee | depart_title|
map<string,array<string>> |
+-------------+-------------+-------------+------------------+
- 将数据加载到表中:
> LOAD DATA INPATH '/tmp/hivedemo/data/employee.txt'
> OVERWRITE INTO TABLE employee;
No rows affected (1.023 seconds)
- 查询表中的整个数组和每个数组元素:
> SELECT work_place FROM employee;
+----------------------+
| work_place |
+----------------------+
| [Montreal, Toronto] |
| [Montreal] |
| [New York] |
| [Vancouver] |
+----------------------+
4 rows selected (27.231 seconds)
> SELECT
> work_place[0] as col_1, work_place[1] as col_2,
> work_place[2] as col_3
> FROM employee;
+------------+----------+--------+
| col_1 | col_2 | col_3 |
+------------+----------+--------+
| Montreal | Toronto | |
| Montreal | | |
| New York | | |
| Vancouver | | |
------------+----------+---------+
4 rows selected (24.689 seconds)
- 查询表中的整个结构和每个结构属性:
> SELECT gender_age FROM employee;
+------------------+
| gender_age |
+------------------+
| [Male, 30] |
| [Male, 35] |
| [Female, 27] |
| [Female, 57] |
+------------------+
4 rows selected (28.91 seconds)
> SELECT gender_age.gender, gender_age.age FROM employee;
+------------+------+
| gender | age |
+------------+------+
| Male | 30 |
| Male | 35 |
| Female | 27 |
| Female | 57 |
+------------+------+
4 rows selected (26.663 seconds)
- 查询表中的整个地图和每个地图元素:
> SELECT skills_score FROM employee;
+--------------------+
| skills_score |
+--------------------+
| {DB=80} |
| {Perl=85} |
| {Python=80} |
| {Sales=89, HR=94} |
+--------------------+
4 rows selected (32.659 seconds)
> SELECT
> name, skills_score['DB'] as DB, skills_score['Perl'] as Perl,
> skills_score['Python'] as Python,
> skills_score['Sales'] as Sales,
> skills_score['HR'] as HR
> FROM employee;
+----------+-----+-------+---------+--------+-----+
| name | db | perl | python | sales | hr |
+----------+-----+-------+---------+--------+-----+
| Michael | 80 | | | | |
| Will | | 85 | | | |
| Shelley | | | 80 | | |
| Lucy | | | | 89 | 94 |
+----------+-----+-------+---------+--------+-----+
4 rows selected (24.669 seconds)
Note that the column name shown in the result or in the hive statement is not case sensitive. It is always shown in lowercase letters.
- 查询表中的复合类型:
> SELECT depart_title FROM employee;
+---------------------------------+
| depart_title |
+---------------------------------+
| {Product=[Developer, Lead]} |
| {Test=[Lead], Product=[Lead]} |
| {Test=[Lead], COE=[Architect]} |
| {Sales=[Lead]} |
+---------------------------------+
4 rows selected (30.583 seconds)
> SELECT
> name, depart_title['Product'] as Product, depart_title['Test']
as Test,
> depart_title['COE'] as COE, depart_title['Sales'] as Sales
> FROM employee;
+--------+--------------------+---------+-------------+------+
| name | product | test | coe |sales |
+--------+--------------------+---------+-------------+------+
| Michael| [Developer, Lead] | | | |
| Will | [Lead] | [Lead] | | |
| Shelley| | [Lead] | [Architect] | |
| Lucy | | | |[Lead]|
+--------+--------------------+---------+-------------+------+
4 rows selected (26.641 seconds)
> SELECT
> name, depart_title['Product'][0] as product_col0,
> depart_title['Test'][0] as test_col0
> FROM employee;
+----------+---------------+------------+
| name | product_col0 | test_col0 |
+----------+---------------+------------+
| Michael | Developer | |
| Will | Lead | Lead |
| Shelley | | Lead |
| Lucy | | |
+----------+---------------+------------+
4 rows selected (26.659 seconds)
The default delimiters in table-creation DDL are as follows:
- 行分隔符:可配合Ctrl+A或^A 使用(建表时使用\001 和)
- 收款项目分隔符:可配合Ctrl+B或^B(\002)使用
- 映射键分隔符:可与Ctrl+C或^C(\003)配合使用
If the delimiter is overridden during the table creation, it only works when used in the flat structure. This is still a limitation in Hive described in Apache Jira Hive-365 (https://issues.apache.org/jira/browse/HIVE-365). For nested types, the level of nesting determines the delimiter. Using ARRAY
of ARRAY
as an example, the delimiters for the outer ARRAY
, as expected, are Ctrl + *B *characters, but the inner ARRAY
delimiter becomes Ctrl + *C *characters, which is the next delimiter in the list. In the preceding example, the depart_title
column, which is a MAP
of ARRAY
, the MAP
key delimiter is Ctrl + C, and the ARRAY
delimiter is Ctrl + D.
数据类型转换
与 SQL 类似,HQL 同时支持隐式和显式类型转换。 从窄类型到宽类型的基元类型转换称为隐式转换。 但是,不允许反向转换。 所有整数数值类型FLOAT
和STRING
都可以隐式转换为DOUBLE
,而TINYINT
、SMALLINT
和INT
都可以转换为FLOAT
。BOOLEAN
类型不能转换为任何其他类型。 有一个数据类型交叉表,描述了每两个类型之间允许的隐式转换,可以在https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types中找到。 显式类型转换使用带有CAST(value as TYPE)
语法的CAST
函数。 例如,CAST('100' as INT)
会将100
字符串转换为100
整数值。 如果强制转换失败,如CAST('INT' as INT)
,则该函数返回NULL
。
此外,BINARY
类型只能先转换为STRING
,然后根据需要从STRING
转换为其他类型。
数据定义
HIVE 的数据定义语言(DDL)是 HQL 语句的子集,这些语句通过创建、删除或更改模式对象(如数据库、表、视图、分区和存储桶)来描述配置单元数据结构。 大多数 DDL 语句都以CREATE
、DROP
或ALTER
关键字开头。 HQL DDL 的语法与 SQL DDL 非常相似。 在下一节中,我们将重点介绍 HQL DDL 的细节。
HQL uses *--*
before a single line of characters as comments, and it does not support multiline comments until v2.3.0. After v2.3.0, we can use bracketed single or multiline comments between /*
and */
.
数据库
配置单元中的数据库描述了用于类似目的或属于相同组的表的集合。 如果未指定数据库,则使用文件default
数据库,并将 HDFS和中的文件/user/hive/warehouse
用作其根目录。 此路径可通过hive-site.xml
中的hive.metastore.warehouse.dir
属性进行配置。每当创建新数据库时,配置单元都会在/user/hive/warehouse
下为每个数据库创建一个新目录。 例如,myhivebook
数据库位于/user/hive/datawarehouse/myhivebook.db
。 此外,DATABASE
还有一个别名**SCHEMA**
,,表示它们在 HQL 中是相同的。以下是数据库操作的主要 DDL:
- 如果数据库/架构不存在,请创建该数据库/架构:
> CREATE DATABASE myhivebook;
> CREATE SCHEMA IF NOT EXISTS myhivebook;
- 使用位置、注释和元数据信息创建数据库:
> CREATE DATABASE IF NOT EXISTS myhivebook
> COMMENT 'hive database demo'
> LOCATION '/hdfs/directory'
> WITH DBPROPERTIES ('creator'='dayongd','date'='2018-05-01');
-- To show the DDL use show create database since v2.1.0
> SHOW CREATE DATABASE default;
+------------------------------------------------+
| createdb_stmt |
+------------------------------------------------+
| CREATE DATABASE `default` |
| COMMENT |
| 'Default Hive database' |
| LOCATION |
| 'hdfs://localhost:9000/user/hive/warehouse' |
+------------------------------------------------+
- 使用通配符显示和描述数据库:
> SHOW DATABASES;
+----------------+
| database_name |
+----------------+
| default |
+----------------+
1 row selected (1.7 seconds)
> SHOW DATABASES LIKE 'my.*';
> DESCRIBE DATABASE default;
+-------+----------------------+--------------------------------+
|db_name| comment | location |
+-------+----------------------+--------------------------------+
|default|Default Hive database | hdfs://localhost:9000
/user/hive/warehouse |
+-------+----------------------+--------------------------------+
1 row selected (1.352 seconds)
- 切换为使用一个数据库或直接使用数据库名称限定表名:
> USE myhivebook;
> --SELECT * FROM myhivebook.table_name;
- 显示当前数据库:
> SELECT current_database();
+----------+
| _c0 |
+----------+
| default |
+----------+
1 row selected (0.218 seconds)
- 删除数据库:
> DROP DATABASE IF EXISTS myhivebook;--failed when database is
not empty
> DROP DATABASE IF EXISTS myhivebook CASCADE;--drop database and
tables
Hive databases/tables are directories/subdirectories in HDFS. In order to remove the database directory, we need to remove the subdirectories (for tables) first. By default, the database cannot be dropped if it is not empty, unless the CASCADE option is specified. With this option, it drops all tables in the database automatically before dropping the database.
- 更改数据库属性。
ALTER DATABASE
语句只能应用于数据库上的dbproperties
、owner
和location
。 其他数据库属性不能更改:
> ALTER DATABASE myhivebook SET DBPROPERTIES ('edited-
by'='Dayong');
> ALTER DATABASE myhivebook SET OWNER user dayongd;
> ALTER DATABASE myhivebook SET LOCATION '/tmp/data/myhivebook';
Since Hive v2.2.1, the ALTER DATABASE ... SET LOCATION
statement can be used to modify the database's location, but it does not move all existing tables/partitions in the current database directory to the newly specified location. It only changes the location for newly added tables after the database is altered. This behavior is analogous to how changing a table-directory does not move existing partitions to a different location. The SHOW
and DESC
(or DESCRIBE
) statements in Hive are used to show the definition for most of the objects, such as tables and partitions. The SHOW
statement supports a wide range of Hive objects, such as tables, tables' properties, table DDL, index, partitions, columns, functions, locks, roles, configurations, transactions, and compactions. The DESC
statement supports a small range of Hive objects, such as databases, tables, views, columns, and partitions. However, the DESC
statement is able to provide more detailed information combined with the EXTENDED
or FORMATTED
keywords. In this book, there is no dedicated section to introduce SHOW
and DESC
. Instead, we introduce them in line with other HQL through the remaining chapters.
桌子 / 列表 / 表格 / 平面
配置单元中的表的概念与关系数据库中的表非常相似。 每个表都映射到一个目录,在 HDFS 中,该目录默认位于/user/hive/warehouse
目录下。 例如,为employee
表格创建了/user/hive/warehouse/employee
。 表中的所有数据都存储在此配置单元用户可管理的目录中(完全权限)。 这类表称为内部表或托管表。 当数据已经存储在 HDFS 中时,可以创建一个外部表来描述数据。 之所以称为external
,是因为外部表中的数据是在LOCATION
属性中指定的,而不是默认的仓库目录。 在内部表中保存数据时,表完全管理其中的数据。 当内部表被删除时,它的数据将被一起删除。 但是,删除外部表时,不会删除数据。 将外部表用于源只读数据或将处理后的数据共享给数据消费者是相当常见的,给出了定制的 HDFS 位置。另一方面,内部表在数据处理过程中经常被用作中间表,因为在 HQL 的支持下,它是相当强大和灵活的。
表创建
以下是用于创建内部和外部表的主要 DDL:
- 显示
employee.txt
的数据文件内容:
$ vi /home/hadoop/employee.txt
Michael|Montreal,Toronto|Male,30|DB:80|Product:Developer^DLead
Will|Montreal|Male,35|Perl:85|Product:Lead,Test:Lead
Shelley|New York|Female,27|Python:80|Test:Lead,COE:Architect
Lucy|Vancouver|Female,57|Sales:89,HR:94|Sales:Lead
- 创建内部表并加载数据:
> CREATE TABLE IF NOT EXISTS employee_internal (
> name STRING COMMENT 'this is optinal column comments',
> work_place ARRAY<STRING>,-- table column names are NOT case
sensitive
> gender_age STRUCT<gender:STRING,age:INT>,
> skills_score MAP<STRING,INT>, -- columns names are lower case
> depart_title MAP<STRING,ARRAY<STRING>>-- No "," for the last
column
> )
> COMMENT 'This is an internal table'-- This is optional table
comments
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|' -- Symbol to seperate columns
> COLLECTION ITEMS TERMINATED BY ','-- Seperate collection elements
> MAP KEYS TERMINATED BY ':' -- Symbol to seperate keys and values
> STORED as TEXTFILE; -- Table file format
No rows affected (0.149 seconds)
> LOAD DATA INPATH '/tmp/hivedemo/data/employee.txt'
> OVERWRITE INTO TABLE employee_internal;
If the folder path does not exist in the LOCATION
property, Hive will create that folder. If there is another folder inside it, Hive will NOT report errors when creating the table but querying the table.
- 创建外部表并加载数据:
> CREATE EXTERNAL TABLE employee_external ( -- Use EXTERNAL keywords
> name string,
> work_place ARRAY<string>,
> gender_age STRUCT<gender:string,age:int>,
> skills_score MAP<string,int>,
> depart_title MAP<STRING,ARRAY<STRING>>
> )
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|'
> COLLECTION ITEMS TERMINATED BY ','
> MAP KEYS TERMINATED BY ':'
> STORED as TEXTFILE
> LOCATION '/user/dayongd/employee'; -- Specify data folder location
No rows affected (1.332 seconds)
> LOAD DATA INPATH '/tmp/hivedemo/data/employee.txt'
> OVERWRITE INTO TABLE employee_external;
Since v2.1.0, Hive supports primary and foreign key constraints. However, these constraints are not validated, so the upstream system needs to ensure data integrity before it's loaded into Hive. The Hive constraints may benefit some SQL tools to generate more efficient queries with them, but they are not used very often.
HIVE 还支持创建临时表。临时表只对当前用户会话可见。 它会在会话结束时自动删除。 临时表的数据通常存储在用户的临时目录中,如/tmp/hive-<username>
。 因此,当您的临时表中有敏感数据时,请确保文件夹配置正确或安全可靠。如果临时表与永久表同名,则会选择临时表,而不是永久表。 临时表不支持分区和索引。 以下是创建临时表的三种方式:
> CREATE TEMPORARY TABLE IF NOT EXISTS tmp_emp1 (
> name string,
> work_place ARRAY<string>,
> gender_age STRUCT<gender:string,age:int>,
> skills_score MAP<string,int>,
> depart_title MAP<STRING,ARRAY<STRING>>
> );
No rows affected (0.122 seconds)
> CREATE TEMPORARY TABLE tmp_emp2 as SELECT * FROM tmp_emp1;
> CREATE TEMPORARY TABLE tmp_emp3 like tmp_emp1;
还可以在一条称为 Create-Table-as-Select(CTAS)的语句中使用查询结果创建和填充表。 在填充所有查询结果之前,CTAS 创建的表对其他用户不可见。 CTA 有以下限制:
- 创建的表不能是分区表
- 创建的表不能是外部表
- 创建的表不能是列表分类表
尽管SELECT *
语句本身不会触发任何 Yarn 作业,但 CCTASSELECT *
语句始终会触发 Yarn 作业来填充数据。
CTA 还可以与CTE一起使用,CTE表示CommonTableExpression。CTE 是一个临时结果集,派生自一个在WITH
子句中指定的简单 SELECT 查询,后跟一个SELECT
语句或一个INSERT
语句来构建结果集。 CTE 仅在单个语句的执行范围内定义。 一个或多个 CTE 可以嵌套或链接的方式与关键字一起使用,如SELECT
、INSERT
、CREATE TABLE AS SELECT
或CREATE VIEW AS SELECT
语句。 使用 HQL 的 CTE 使查询比编写复杂的嵌套查询更简洁、更清晰。
以下是使用 CTAS 和 CTE 创建表的示例:
- 使用 CTAS 创建表格:
> CREATE TABLE ctas_employee as SELECT * FROM employee_external;
No rows affected (1.562 seconds)
- 创建同时包含 CTAS 和 CTE 的表格:
> CREATE TABLE cte_employee as
> WITH r1 as (
> SELECT name FROM r2 WHERE name = 'Michael'
> ),
> r2 as (
> SELECT name FROM employee WHERE gender_age.gender= 'Male'
> ),
> r3 as (
> SELECT name FROM employee WHERE gender_age.gender= 'Female'
> )
> SELECT * FROM r1
> UNION ALL
> SELECT * FROM r3;
No rows affected (61.852 seconds)
> SELECT * FROM cte_employee;
+----------------------------+
| cte_employee.name |
+----------------------------+
| Michael |
| Shelley |
| Lucy |
+----------------------------+
3 rows selected (0.091 seconds)
- 使用 CTAS 通过从另一个表复制架构来创建空表。 它为空,因为 WHERE 条件为 FALSE:
> CREATE TABLE empty_ctas_employee as
> SELECT * FROM employee_internal WHERE 1=2;
No rows affected (213.356 seconds)
- 另一方面,我们还可以使用
CREATE TABLE LIKE
创建一个空表。 这是一种更快的复制表模式的方法,因为它不会触发任何作业,而只复制元数据:
> CREATE TABLE empty_like_employee LIKE employee_internal;
No rows affected (0.115 seconds)
表说明
由于我们大部分时间都在处理表,因此有几个有用的表信息显示命令,如下所示:
- 显示具有正则表达式筛选器的表:
> SHOW TABLES; -- Show all tables in database
> SHOW TABLES '*sam*'; -- Show tables name contains "sam"
> SHOW TABLES '*sam|lily*'; -- Show tables name contains "sam" or
"lily"
- 列出与给定正则表达式匹配的所有表的详细表信息:
>SHOW TABLE EXTENDED LIKE 'employee_int*';
OK
tableName:employee_internal
owner:dayongd
location:hdfs://localhost/user/hive/warehouse/employee_internal
inputformat:org.apache.hadoop.mapred.TextInputFormat
outputformat:org.apache.hadoop.hive.ql.io.HiveIgnoreKeyText
OutputFormatcolumns:struct columns { i32 num} partitioned:false
partitionColumns:
totalNumberFiles:0
totalFileSize:0
maxFileSize:0
minFileSize:0
lastAccessTime:0
lastUpdateTime:1274517075221
- 以两种方式显示表列信息:
> SHOW COLUMNS IN employee_internal;
+---------------+
| field |
+---------------+
| name |
| work_place |
| gender_age |
| skills_score |
| depart_title |
+---------------+
5 rows selected (0.101 seconds)
> DESC employee_internal;
+--------------+-------------------------------+---------+
| col_name | data_type | comment |
+--------------+-------------------------------+---------+
| name | string | |
| work_place | array<string> | |
| gender_age | struct<gender:string,age:int> | |
| skills_score | map<string,int> | |
| depart_title | map<string,array<string>> | |
+---------------+------------------------------+---------+
5 rows selected (0.127 seconds)
- 显示指定表的 CREATE-TABLE DDL 语句:
> SHOW CREATE TABLE employee_internal;
+--------------------------------------------------------------+
| createtab_stmt |
+--------------------------------------------------------------+
| CREATE TABLE `employee_internal`( |
| `name` string, |
| `work_place` array<string>, |
| `gender_age` struct<gender:string,age:int>, |
| `skills_score` map<string,int>, |
| `depart_title` map<string,array<string>>) |
| COMMENT 'this is an internal table' |
| ROW FORMAT SERDE |
| 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe' |
| WITH SERDEPROPERTIES ( |
| 'colelction.delim'=',', |
| 'field.delim'='|', |
| 'mapkey.delim'=':', |
| 'serialization.format'='|') |
| STORED as INPUTFORMAT |
| 'org.apache.hadoop.mapred.TextInputFormat' |
| OUTPUTFORMAT |
| 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' |
| LOCATION |
| 'hdfs://localhost:9000/user/hive/warehouse/employee_internal'|
| TBLPROPERTIES ( |
| 'transient_lastDdlTime'='1523108773') |
+--------------------------------------------------------------+
22 rows selected (0.191 seconds)
- 显示指定表的表属性:
> SHOW TBLPROPERTIES employee_internal;
+-----------------------+---------------------------+
| prpt_name | prpt_value |
+-----------------------+---------------------------+
| comment | this is an internal table |
| numFiles | 1 |
| numRows | 0 |
| rawDataSize | 0 |
| totalSize | 227 |
| transient_lastDdlTime | 1523108773 |
+-----------------------+---------------------------+
6 rows selected (0.095 seconds)
餐桌清洁
有时,我们可能需要清理表格,要么只删除记录,要么只删除表格和记录。 HQL 中有两个语句用于执行此类清理。 一个是DROP TABLE
语句,另一个是TRUNCATE TABLE
语句。 如果配置了垃圾桶设置,则内部表上的 DROP-TABLE 语句将完全删除该表,并将数据移动到当前用户目录中的.trash
。 外部表上的 DROP-TABLE 语句只会删除表定义,但会保留数据:
> DROP TABLE IF EXISTS empty_ctas_employee;
No rows affected (0.283 seconds)
另一方面,TRUNCATE TABLE 语句只从表中删除数据。 该表仍然存在,但为空。 注意,TRUNCATE TABLE 只能应用于内部表:
> TRUNCATE TABLE cte_employee;-- Only apply to internal tables
No rows affected (0.093 seconds)
> SELECT name FROM cte_employee;--Other hand, the truncate t
-- Not data left, but empty table exists
+--------------------+
| cte_employee.name |
+--------------------+
+--------------------+
No rows selected (0.059 seconds)
表格更改
一旦创建了表,我们仍然可以修改它的元数据,比如添加新列和更改列的数据类型。 在 HQL 中,我们使用ALTER
命令修改表的元数据。 但是,ALTER TABLE 无法相应地更新数据。 我们应该手动确保实际数据符合元数据定义,否则查询将不会返回任何预期的结果。
以下是在 HQL 中更改表格的示例:
- 使用
ALTER
语句重命名表。 这通常用作数据备份:
> ALTER TABLE cte_employee RENAME TO cte_employee_backup;
No rows affected (0.237 seconds)
- 使用
TBLPROPERTIES
更改表属性:
> ALTER TABLE c_employee SET TBLPROPERTIES
('comment'='New comments');
No rows affected (0.239 seconds)
- 使用
SERDEPROPERTIES
:更改表的行格式和 SerDe(SerDe 在章 8,可扩展性注意事项中介绍)
> ALTER TABLE employee_internal SET SERDEPROPERTIES
('field.delim' = '$');
No rows affected (0.148 seconds)
- 使用
FILEFORMAT
更改表的文件格式:
> ALTER TABLE c_employee SET FILEFORMAT RCFILE;
No rows affected (0.235 seconds)
- 使用
LOCATION
更改表的位置,即 HDFS 的完整 URI:
> ALTER TABLE c_employee SET LOCATION
'hdfs://localhost:9000/tmp/employee';
No rows affected (0.169 seconds)
- 启用/禁用表的数据保护;
NO_DROP
或OFFLINE
。NO_DROP
可防止表被删除,而OFFLINE
或可防止查询表中的数据(非元数据):
> ALTER TABLE c_employee ENABLE NO_DROP;
> ALTER TABLE c_employee DISABLE NO_DROP;
> ALTER TABLE c_employee ENABLE OFFLINE;
> ALTER TABLE c_employee DISABLE OFFLINE;
- 在
RCFile
或ORC
表中启用串联(如果它有许多小文件):
> ALTER TABLE c_employee SET FILEFORMAT ORC; -- Convert to ORC
No rows affected (0.160 seconds)
> ALTER TABLE c_employee CONCATENATE;
No rows affected (0.165 seconds)
Since v0.8.0, RCFile
is added to support fast block-level merging of small RCFiles
using the CONCATENATE
option. Since v0.14.0, ORC file is added to support the fast stripe-level merging of small ORC files using the CONCATENATE
option. Other file formats are not supported yet. RCfiles
merge at the block level, while ORC
files merge at the stripe level, thereby avoiding the overhead of decompressing and decoding the data.
- 更改列的数据类型、位置(使用
AFTER
或FIRST
)和注释:
> DESC employee_internal; -- Check column type before alter
+----------------+-------------------------------+----------+
| col_name | data_type | comment |
+----------------+-------------------------------+----------+
| employee_name | string | |
| work_place | array<string> | |
| gender_age | struct<gender:string,age:int> | |
| skills_score | map<string,int> | |
| depart_title | map<string,array<string>> | |
+----------------+-------------------------------+----------+
5 rows selected (0.119 seconds)
> ALTER TABLE employee_internal
> CHANGE name employee_name string AFTER gender_age;
No rows affected (0.23 seconds)
> DESC employee_internal; -- Verify type and order changes above
+----------------+-------------------------------+----------+
| col_name | data_type | comment |
+----------------+-------------------------------+----------+
| work_place | array<string> | |
| gender_age | struct<gender:string,age:int> | |
| employee_name | string | |
| skills_score | map<string,int> | |
| depart_title | map<string,array<string>> | |
+----------------+-------------------------------+----------+
5 rows selected (0.214 seconds)
> ALTER TABLE employee_internal
> CHANGE employee_name name string COMMENT 'updated' FIRST;
No rows affected (0.238 seconds)
> DESC employee_internal; -- Verify changes by FRIST keywords
+---------------+-------------------------------+----------+
| col_name | data_type | comment |
+---------------+-------------------------------+----------+
| name | string | updated |
| work_place | array<string> | |
| gender_age | struct<gender:string,age:int> | |
| skills_score | map<string,int> | |
| depart_title | map<string,array<string>> | |
+---------------+-------------------------------+----------+
5 rows selected (0.119 seconds)
- 向表中添加新列:
> ALTER TABLE c_employee ADD COLUMNS (work string);
No rows affected (0.184 seconds)
> DESC c_employee;
+-----------+------------+----------+
| col_name | data_type | comment |
+-----------+------------+----------+
| name | string | |
| work | string | |
+-----------+------------+----------+
2 rows selected (0.115 seconds)
- 使用指定的新列替换表中的所有列:
> ALTER TABLE c_employee REPLACE COLUMNS (name string);
No rows affected (0.132 seconds)
> DESC c_employee; -- Verify the changes
+-----------+------------+----------+
| col_name | data_type | comment |
+-----------+------------+----------+
| name | string | |
+-----------+------------+----------+
1 row selected (0.129 seconds)
隔断
默认情况下,一个简单的 HQL 查询扫描整个表。 这会降低查询大表时的性能。 这个问题可以通过创建分区来解决,这些分区与 RDBMS 中的分区非常相似。 在配置单元中,每个分区对应于一个预定义的分区列,该列映射到 HDFS 中表目录中的子目录。 当查询表时,只读取表中所需的数据分区(目录),因此大大减少了查询的 I/O 和时间。 使用分区是提高配置单元性能的一种非常简单有效的方法。
以下是在 HQL 中创建分区的示例:
> CREATE TABLE employee_partitioned (
> name STRING,
> work_place ARRAY<STRING>,
> gender_age STRUCT<gender:STRING,age:INT>,
> skills_score MAP<STRING,INT>,
> depart_title MAP<STRING,ARRAY<STRING>> -- This is regular column > )
> PARTITIONED BY (year INT, month INT) -- Use lower case partition column > ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|'
> COLLECTION ITEMS TERMINATED BY ','
> MAP KEYS TERMINATED BY ':';
No rows affected (0.293 seconds)
> DESC employee_partitioned;
-- Partition columns are listed twice
+-------------------------+-------------------------------+---------+
| col_name | data_type | comment |
+-------------------------+-------------------------------+---------+
| name | string | |
| work_place | array<string> | |
| gender_age | struct<gender:string,age:int> | |
| skills_score | map<string,int> | |
| depart_title | map<string,array<string>> | |
| year | int | |
| month | int | |
| | NULL | NULL |
| # Partition Information | NULL | NULL |
| # col_name | data_type | comment |
| | NULL | NULL |
| year | int | |
| month | int | |
+-------------------------+-------------------------------+---------+
13 rows selected (0.38 seconds)
> SHOW PARTITIONS employee_partitioned; -- Check partitions +------------+
| partition |
+------------+
+------------+
No rows selected (0.177 seconds)
从前面的结果可以看出,该分区没有自动启用。 我们必须使用ALTER TABLE ADD PARTITION
语句将静态分区添加到表中。 在这里,静态表示分区是手动添加的。 此命令更改表的元数据,但不加载数据。 如果分区位置中不存在数据,则查询不会返回任何结果。 要删除分区元数据,请使用ALTER TABLE ... DROP PARTITION
语句。 对于外部表,ALTER
不更改数据,但元数据,DROP PARTITION 不会删除分区内的数据。 为了删除数据,我们可以使用hdfs dfs -rm
命令从 HDFS 中删除外部表的数据。 对于内部表,ALTER TABLE ... DROP PARTITION
将同时删除分区和数据。 以下是对分区表进行常见操作的更多示例:
- 执行分区操作,如添加、删除和重命名分区:
> ALTER TABLE employee_partitioned ADD -- Add multiple static
partitions > PARTITION (year=2018, month=11) PARTITION (year=2018,
month=12);
No rows affected (0.248 seconds)
> SHOW PARTITIONS employee_partitioned;
+---------------------+
| partition |
+---------------------+
| year=2018/month=11 |
| year=2018/month=12 |
+---------------------+
2 rows selected (0.108 seconds) -- Drop partition with PURGE at the end will remove completely -- Drop partition will NOT remove data for external table
-- Drop partition will remove data with partition for internal table > ALTER TABLE employee_partitioned
> DROP IF EXISTS PARTITION (year=2018, month=11);
> SHOW PARTITIONS employee_partitioned;
+---------------------+
| partition |
+---------------------+
| year=2018/month=12 |
+---------------------+
1 row selected (0.107 seconds)
> ALTER TABLE employee_partitioned
> DROP IF EXISTS PARTITION (year=2017); -- Drop all partitions in
2017
> ALTER TABLE employee_partitioned
> DROP IF EXISTS PARTITION (month=9); -- Drop all month is 9
> ALTER TABLE employee_partitioned -- Rename exisiting partition
values
> PARTITION (year=2018, month=12)
> RENAME TO PARTITION (year=2018,month=10);
No rows affected (0.274 seconds)
> SHOW PARTITIONS employee_partitioned;
+---------------------+
| partition |
+---------------------+
| year=2018/month=10 |
+---------------------+
2 rows selected (0.274 seconds)
-- Below is failed
-- Because all partition columns should be specified for partition
rename
> --ALTER TABLE employee_partitioned PARTITION (year=2018)
> --RENAME TO PARTITION (year=2017);
- 创建分区后,将数据加载到表分区:
> LOAD DATA INPATH '/tmp/hivedemo/data/employee.txt'
> OVERWRITE INTO TABLE employee_partitioned
> PARTITION (year=2018, month=12);
No rows affected (0.96 seconds)
> SELECT name, year, month FROM employee_partitioned; -- Verify data
loaded
+----------+-------+--------+
| name | year | month |
+----------+-------+--------+
| Michael | 2018 | 12 |
| Will | 2018 | 12 |
| Shelley | 2018 | 12 |
| Lucy | 2018 | 12 |
+----------+-------+--------+
4 rows selected (37.451 seconds)
为了避免手动添加静态分区,动态分区插入(或多分区插入)设计用于在扫描输入表时动态确定应该添加和填充哪些分区。 此部分在第 5 章、数据操作中的INSERT
语句中进行了更详细的介绍。 要在分区中填充数据,我们可以使用LOAD
或INSERT
语句。 该语句仅加载指定分区列表中的数据。
虽然分区列映射到目录名而不是数据,但我们可以像 HQL 中的常规列一样查询或选择它们,以缩小结果集。
The use case for static and dynamic partition is quite different. Static partition is often used for an external table containing data newly landed in HDFS. In this case, it often uses the date, such as yyyyMMdd
, as the partition column. Whenever the data of the new day arrives, we add the day-specific static partition (by script) to the table, and then the newly arrived data is queryable from the table immediately. For dynamic partition, it is often being used for data transformation between internal tables with partition columns derived from data itself; see Chapter 5, Data Manipulation.
- 从分区中删除数据。 注意,删除数据不会删除分区信息。 为了进行完整的数据清理,我们可以在删除数据后删除步骤 1 中描述的分区:
-- For internal table, we use truncate
> TRUNCATE TABLE employee_partitioned PARTITION
(year=2018,month=12);
-- For external table, we have to use hdfs command
> dfs -rm -r -f /user/dayongd/employee_partitioned;
- 向分区表添加常规列。 注意,我们需要
CANNOT
添加新列作为分区列。 在分区表中添加/删除列时有两个选项:CASCADE
和RESTRICT
。 常用的 _CASCADE
选项将相同的更改级联到表中的所有分区。 但是,RESTRICT
和是默认设置,将列更改限制为仅对表元数据进行更改,这意味着更改将仅应用于新分区,而不会应用于现有分区:
> ALTER TABLE employee_partitioned ADD COLUMNS (work string)
CASCADE;
- 我们可以更改现有的分区列数据类型:
> ALTER TABLE employee_partitioned PARTITION COLUMN(year string);
No rows affected (0.274 seconds)
> DESC employee_partitioned; -- Verify the changes
+-------------------------+-------------------------------+---------+
| col_name | data_type | comment |
+-------------------------+-------------------------------+---------+
| name | string | |
| work_place | array<string> | |
| gender_age | struct<gender:string,age:int> | |
| skills_score | map<string,int> | |
| depart_title | map<string,array<string>> | |
| work | string | |
| year | int | |
| month | int | |
| | NULL | NULL |
| # Partition Information | NULL | NULL |
| # col_name | data_type | comment |
| | NULL | NULL |
| year | string | |
| month | int | |
+-------------------------+-------------------------------+---------+
13 rows selected (0.38 seconds)
Right now, we can only change the partition column data type. We cannot add/remove a column from partition columns. If we have to change the partition design, we must back up and recreate the table, and then migrate the data. In addition, we are NOT
able to change a non-partition table to a partition table directly.
- 在文件格式、位置、保护和串联方面更改分区的其他属性与更改 TABLE 语句的语法相同:
> ALTER TABLE employee_partitioned PARTITION (year=2018)
> SET FILEFORMAT ORC;
> ALTER TABLE employee_partitioned PARTITION (year=2018)
> SET LOCATION '/tmp/data';
> ALTER TABLE employee_partitioned PARTITION (year=2018) ENABLE
NO_DROP;
> ALTER TABLE employee_partitioned PARTITION (year=2018) ENABLE
OFFLINE;
> ALTER TABLE employee_partitioned PARTITION (year=2018) DISABLE
NO_DROP;
> ALTER TABLE employee_partitioned PARTITION (year=2018) DISABLE
OFFLINE;
> ALTER TABLE employee_partitioned PARTITION (year=2018) CONCATENATE;
桶 / 一桶的量 / 戽斗 / 铲斗
除了分区之外,存储桶是将数据集聚集到更易于管理的部分以优化查询性能的另一种技术。 与分区不同,存储桶对应 HDFS 中的文件段。 例如,上一节中的employee_partitioned
表使用year
和month
作为顶级分区。 如果进一步请求使用employee_id
作为第三级分区,则会创建许多分区目录。 例如,我们可以使用employee_id
作为存储桶列来存储employee_partitioned
表。 此列的值将按用户定义的存储桶数进行散列。 具有相同employee_id
的记录将始终存储在同一存储桶(文件段)中。 存储桶列由CLUSTERED BY
关键字定义。 它与分区列有很大不同,因为分区列指的是目录,而存储桶列必须是实际的表数据列。 通过使用存储桶,HQL 查询可以轻松高效地执行采样(参见第 6 章、数据聚合和采样)、存储桶端连接和映射端连接(参见第 4 章、数据关联和范围)。 创建存储桶表的示例如下所示:
--Prepare table employee_id and its dataset to populate bucket table
> CREATE TABLE employee_id (
> name STRING,
> employee_id INT,
> work_place ARRAY<STRING>,
> gender_age STRUCT<gender:STRING,age:INT>,
> skills_score MAP<STRING,INT>,
> depart_title MAP<STRING,ARRAY<STRING>>
> )
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|'
> COLLECTION ITEMS TERMINATED BY ','
> MAP KEYS TERMINATED BY ':';
No rows affected (0.101 seconds)
> LOAD DATA INPATH
> '/tmp/hivedemo/data/employee_id.txt'
> OVERWRITE INTO TABLE employee_id
No rows affected (0.112 seconds)
--Create the buckets table > CREATE TABLE employee_id_buckets (
> name STRING,
> employee_id INT, -- Use this table column as bucket column later
> work_place ARRAY<STRING>,
> gender_age STRUCT<gender:string,age:int>,
> skills_score MAP<string,int>,
> depart_title MAP<string,ARRAY<string >>
> )
> CLUSTERED BY (employee_id) INTO 2 BUCKETS -- Support more columns > ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|'
> COLLECTION ITEMS TERMINATED BY ','
> MAP KEYS TERMINATED BY ':';
No rows affected (0.104 seconds)
To define the proper number of buckets, we should avoid having too much or too little data in each bucket. A better choice is somewhere near two blocks of data, such as 512 MB of data in each bucket. As a best practice, use 2N as the number of buckets.
分组与数据加载过程密切相关。 要将数据正确加载到时段表中,我们需要将最大减数设置为与创建表中指定的时段数相同的时段数(例如,2),或者启用强制分组(推荐),如下所示:
> set map.reduce.tasks = 2;
No rows affected (0.026 seconds)
> set hive.enforce.bucketing = true; -- This is recommended No rows affected (0.002 seconds)
要将数据填充到存储桶表中,我们不能使用LOAD DATA
语句,因为它不会根据元数据验证数据。 相反,应该始终使用INSERT
来填充桶表:
> INSERT OVERWRITE TABLE employee_id_buckets SELECT * FROM employee_id;
No rows affected (75.468 seconds)
-- Verify the buckets in the HDFS from shell $hdfs dfs -ls /user/hive/warehouse/employee_id_buckets
Found 2 items
-rwxrwxrwx 1 hive hive 900 2018-07-02 10:54
/user/hive/warehouse/employee_id_buckets/000000_0
-rwxrwxrwx 1 hive hive 582 2018-07-02 10:54
/user/hive/warehouse/employee_id_buckets/000001_0
观点
视图是逻辑数据结构,可用于通过隐藏复杂性(如联接、子查询和筛选器)来简化查询。 之所以称其为逻辑,是因为视图仅在metastore
中定义,没有在 HDFS 中占用空间。 与关系数据库中的不同,HQL 中的视图不存储数据或实现。 创建视图后,其架构将立即冻结。 对基础表的后续更改(例如,添加列)不会反映在视图的架构中。 如果基础表被删除或更改,则后续查询无效视图的尝试将失败。 此外,视图是只读的,不能用作LOAD
/INSERT
/ALTER
语句的目标。
以下是视图创建语句的示例:
> CREATE VIEW IF NOT EXISTS employee_skills
> AS
> SELECT
> name, skills_score['DB'] as DB,
> skills_score['Perl'] as Perl,
> skills_score['Python'] as Python,
> skills_score['Sales'] as Sales,
> skills_score['HR'] as HR
> FROM employee;
No rows affected (0.253 seconds)
创建视图时,不会触发 Yarn 作业,因为这只是元数据更改。 但是,该作业将在查询视图时触发。 要检查视图定义,可以使用SHOW
语句。 在修改视图定义时,我们可以使用ALTER VIEW
语句。 以下是显示、检查和修改视图的一些示例:
- 仅显示数据库中的视图。 这是在配置单元 v2.2.0 中引入的。 我们可以在早期版本的配置单元中使用
SHOW TABLES
语句:
> SHOW VIEWS;
> SHOW VIEWS 'employee_*';
No rows affected (0.19 seconds)
- 显示视图的定义:
> DESC FORMATTED employee_skills;
> SHOW CREATE TABLE employee_skills; -- this is recommended
No rows affected (0.19 seconds)
- 更改视图的属性:
> ALTER VIEW employee_skills SET TBLPROPERTIES ('comment'='A
view');
No rows affected (0.19 seconds)
- 重新定义视图:
> ALTER VIEW employee_skills as SELECT * from employee;
No rows affected (0.17 seconds)
- 删除视图:
> DROP VIEW employee_skills;
No rows affected (0.156 seconds)
在 HQL 中有一个特殊的视图,称为LateralView
。 通常与配置单元中的自定义制表函数配合使用,如explode()
、用于数据归一化或处理 JSON 数据。LateralView
首先将制表函数应用于数据,然后将函数的输入和输出连接在一起。 请参阅以下示例:
> SELECT name, workplace FROM employee_internal
> LATERAL VIEW explode(work_place) wp as workplace;
+---------+-----------+
| name | workplace |
+---------+-----------+
| Michael | Montreal |
| Michael | Toronto |
| Will | Montreal |
| Shelley | Montreal |
| Lucy | Vancouver |
+---------+-----------+
5 rows selected (6.693 seconds)
通过在LATERAL VIEW
后添加OUTER
和,即使表格生成函数的输出是NULL
,我们也可以确保生成结果:
> SELECT name, workplace FROM employee_internal
> LATERAL VIEW explode(split(null, ',')) wp as workplace;
+-------+------------+
| name | workplace |
+-------+------------+
+-------+------------+
No rows selected (5.499 seconds)
> SELECT name, workplace FROM employee_internal
> LATERAL VIEW OUTER explode(split(null, ',')) wp as workplace;
+---------+-----------+
| name | workplace |
+---------+-----------+
| Michael | NULL |
| Michael | NULL |
| Will | NULL |
| Shelley | NULL |
| Lucy | NULL |
+---------+-----------+
5 rows selected (5.745 seconds)
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,我们学习了如何在配置单元中定义和使用各种数据类型。 我们了解了如何创建、更改和删除表、分区和视图。 我们还介绍了如何使用外部表、内部表、分区、存储桶和视图。
在下一章中,我们将深入探讨 Hive 中查询数据的细节。
四、数据关联和范围
本章介绍如何通过投影、链接和限制数据范围或作用域来发现数据。 本章主要介绍操作数据集的语句SELECT
、WHERE
、LIMIT
、JOIN
和UNION
/UNION ALL
的语法和用法。
在本章中,我们将介绍以下主题:
- 使用
SELECT
投影数据 - 使用
WHERE
、LIMIT
和LIMIT
等条件过滤数据 - 将数据与
JOIN
链接 - 将数据与
UNION
组合
使用 SELECT 的项目数据
配置单元最常见的用例是在 Hadoop 中查询数据。 要实现这一点,我们需要编写并执行一条SELECT
语句。 SELECT
语句执行的典型工作是在有或没有条件的情况下从表中投影整行(带SELECT *
)或指定列(带带SELECT column1, column2, ...
)。最简单的带SELECT
语句不会触发 Yarn 作业。 相反,转储任务只是为了转储数据而创建的,例如hdfs dfs -cat
命令。SELECT
语句经常与关键字FROM
和DISTINCT
和一起使用。 FROM
关键字后跟表格是SELECT
投影数据的位置。 在SELECT
之后使用的关键字DISTINCT
确保从表中只返回唯一的行或列的组合。 此外,SELECT
还支持与用户定义函数、IF()
语句或CASE WHEN THEN ELSE END
语句和正则表达式相结合的列。以下是使用SELECT
语句投影数据的示例:
- 查询表中的整行或特定列:
> SELECT * FROM employee; -- Project the whole row
> SELECT name FROM employee; -- Project specified columns
+----------+
| name |
+----------+
| Michael |
| Will |
| Shelley |
| Lucy |
+----------+
4 rows selected (0.452 seconds)
-- List all columns match java regular expression > SET hive.support.quoted.identifiers = none; -- Enable this
> SELECT `^work.*` FROM employee; -- All columns start with work
+------------------------+
| employee.work_place |
+------------------------+
| ["Montreal","Toronto"] |
| ["Montreal"] |
| ["New York"] |
| ["Vancouver"] |
+------------------------+
4 rows selected (0.141 seconds)
- 从表中选择列出的不同列:
> SELECT DISTINCT name, work_place FROM employee;
+---------+------------------------+
| name | work_place |
+---------+------------------------+
| Lucy | ["Vancouver"] |
| Michael | ["Montreal","Toronto"] |
| Shelley | ["New York"] |
| Will | ["Montreal"] |
+---------+------------------------+
4 rows selected (35.962 seconds)
- 使用
IF
或CASE WHEN
函数选择列:
> SELECT
> CASE WHEN gender_age.gender = 'Female' THEN 'Ms.'
> ELSE 'Mr.' END as title,
> name,
> IF(array_contains(work_place, 'New York'), 'US', 'CA') as
country
> FROM employee;
+-------+---------+---------+
| title | name | country |
+-------+---------+---------+
| Mr. | Michael | CA |
| Mr. | Will | CA |
| Ms. | Shelley | US |
| Ms. | Lucy | CA |
+-------+---------+---------+
4 rows selected (0.585 seconds)
多个SELECT
语句可以协同工作,使用嵌套查询或 CTE 构建复杂查询。 嵌套查询也称为子查询,是从另一个查询的结果投影数据的查询。可以使用带有WITH
和AS
关键字的 CTE(在第 3 章、数据定义和说明中提到)重写嵌套查询。 使用嵌套查询时,需要为内部查询指定别名(参见下例中的t1
),否则配置单元会报告异常。以下是在 HQL 中使用嵌套查询的几个示例:
- 使用强制别名的嵌套查询示例:
> SELECT
> name, gender_age.gender as gender
> FROM (
> SELECT * FROM employee WHERE gender_age.gender = 'Male'
> ) t1; -- t1 here is mandatory
+----------+----------+
| name | gender |
+----------+----------+
| Michael | Male |
| Will | Male |
+----------+----------+
2 rows selected (48.198 seconds)
- 可以使用 CTE 重写嵌套查询,如下所示。 这是编写复杂的单个 HQL 查询的推荐方式:
> WITH t1 as (
> SELECT * FROM employee WHERE gender_age.gender = 'Male'
> )
> SELECT name, gender_age.gender as gender
> FROM t1;
+----------+----------+
| name | gender |
+----------+----------+
| Michael | Male |
| Will | Male |
+----------+----------+
2 rows selected (38.706 seconds)
此外,后跟常量表达式的特殊SELECT
可以在没有FROM table
子句的情况下工作。 它返回表达式的结果。 这等效于使用一条虚拟记录查询虚拟表:
> SELECT concat('1','+','3','=',cast((1 + 3) as string)) as res;
+-------+
| res |
+-------+
| 1+3=4 |
+-------+
1 row selected (0.109 seconds)
使用条件过滤数据
通过使用条件子句(如LIMIT
、WHERE
、IN
/NOT IN
和EXISTS
/NOT EXISTS
来缩小结果集的范围是很常见的。关键字LIMIT
限制随机返回的指定行数。与LIMIT
相比,WHERE
是一个功能更强大、更通用的条件子句,可以通过表达式、函数和嵌套查询来限制返回的结果集,如以下示例所示:
> SELECT name FROM employee LIMIT 2;
+----------+
| name |
+----------+
| Lucy |
| Michael |
+----------+
2 rows selected (71.125 seconds)
> SELECT name, work_place FROM employee WHERE name = 'Michael';
+----------+------------------------+
| name | work_place |
+----------+------------------------+
| Michael | ["Montreal","Toronto"] |
+----------+------------------------+
1 row selected (38.107 seconds)
-- All the conditions can use together and use after WHERE
> SELECT name, work_place FROM employee WHERE name = 'Michael' LIMIT 1;
+----------+------------------------+
| name | work_place |
+----------+------------------------+
| Michael | ["Montreal","Toronto"] |
+----------+------------------------+
1 row selected (39.103 seconds)
IN
/NOT IN
用作检查值是否属于由IN
或NOT IN
指定的集合的表达式。 从配置单元 v2.1.0 开始,IN
语句和NOT IN
语句支持多列:
> SELECT name FROM employee WHERE gender_age.age in (27, 30);
+----------+
| name |
+----------+
| Michael |
| Shelley |
+----------+
2 rows selected (0.3 seconds)
-- With multiple columns support after v2.1.0
> SELECT
> name, gender_age
> FROM employee
> WHERE (gender_age.gender, gender_age.age) IN
> (('Female', 27), ('Male', 27 + 3)); -- Also support expression
+---------+------------------------------+
| name | gender_age |
+---------+------------------------------+
| Michael | {"gender":"Male","age":30} |
| Shelley | {"gender":"Female","age":27} |
+---------+------------------------------+
2 rows selected (0.282 seconds)
此外,筛选数据还可以在带IN
/NOT IN
和EXISTS
/NOT EXISTS
的WHERE
子句中使用子查询。 使用EXISTS
语句或NOT EXISTS
语句的子查询必须同时引用内部表达式和外部表达式:
> SELECT
> name, gender_age.gender as gender
> FROM employee
> WHERE name IN
> (SELECT name FROM employee WHERE gender_age.gender = 'Male');
+----------+----------+
| name | gender |
+----------+----------+
| Michael | Male |
| Will | Male |
+----------+----------+
2 rows selected (54.644 seconds)
> SELECT
> name, gender_age.gender as gender
> FROM employee a
> WHERE EXISTS (
> SELECT *
> FROM employee b
> WHERE
> a.gender_age.gender = b.gender_age.gender AND
b.gender_age.gender = 'Male'
> ); -- This likes join table a and b with column gender +----------+----------+
| name | gender |
+----------+----------+
| Michael | Male |
| Will | Male |
+----------+----------+
2 rows selected (69.48 seconds)
对于在WHERE
子句中使用的子查询,还有其他限制:
- 子查询只能出现在
WHERE
个子句的右侧 - 不允许嵌套子查询
- 子查询中的
IN
/NOT IN
只支持使用单个列,尽管它们在正则表达式中支持更多列
使用连接链接数据
JOIN
用于将两个或多个表中的行链接到一起。 HIVE 支持大多数 SQLJOIN
操作,如INNER JOIN
和OUTER JOIN
。 此外,HQL 还支持一些特殊的连接,如 MapJoin 和 Semi-Join。 在较早的版本中,Hive 只支持 EQUAL JOIN,v2.2.0 之后,也支持 UEQUAL JOIN和。 但是,在使用不相等联接时应该更加小心,除非您知道预期会发生什么,因为不相等联接可能会通过生成联接表的笛卡尔乘积来返回很多行。 当您想要限制联接的输出时,您应该在联接之后应用WHERE
子句,因为JOIN
发生在WHERE
子句之前。*如果可能,在联接条件上推入筛选条件,而不是 WHERE 条件,以便更早地筛选数据。此外,所有类型的左/右联接都不是完全交换的,总是左/右关联的,而INNER
和FULL OUTER JOINS
既是交换的,也是关联的。
内连接
INNER JOIN
或JOIN
f 从联接表的两侧返回满足联接条件的行。 也可以通过逗号分隔的表名省略关键字和JOIN
;这称为implicit join
。 以下是 HQL 联接操作的示例:
- 首先,准备一个要连接的表,并将数据加载到其中:
> CREATE TABLE IF NOT EXISTS employee_hr (
> name string,
> employee_id int,
> sin_number string,
> start_date date
> )
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|';
No rows affected (1.732 seconds)
> LOAD DATA INPATH '/tmp/hivedemo/data/employee_hr.txt'
> OVERWRITE INTO TABLE employee_hr;
No rows affected (0.635 seconds)
- 在具有相等和不相等联接条件的两个表之间执行
INNER JOIN
运算,以及复杂表达式和 POST JOINWHERE
条件。 通常,我们需要在联接条件中的列之前添加表名或表别名,尽管配置单元总是尝试解析它们:
> SELECT
> emp.name, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph ON emp.name = emph.name; -- Equal Join
+-----------+------------------+
| emp.name | emph.sin_number |
+-----------+------------------+
| Michael | 547-968-091 |
| Will | 527-948-090 |
| Lucy | 577-928-094 |
+-----------+------------------+
3 rows selected (71.083 seconds)
> SELECT
> emp.name, emph.sin_number
> FROM employee emp -- Unequal join supported since v2.2.0
returns morerows
> JOIN employee_hr emph ON emp.name != emph.name;
+----------+-----------------+
| emp.name | emph.sin_number |
+----------+-----------------+
| Michael | 527-948-090 |
| Michael | 647-968-598 |
| Michael | 577-928-094 |
| Will | 547-968-091 |
| Will | 647-968-598 |
| Will | 577-928-094 |
| Shelley | 547-968-091 |
| Shelley | 527-948-090 |
| Shelley | 647-968-598 |
| Shelley | 577-928-094 |
| Lucy | 547-968-091 |
| Lucy | 527-948-090 |
| Lucy | 647-968-598 |
+----------+-----------------+
13 rows selected (24.341 seconds)
-- Join with complex expression in join condition
-- This is also the way to implement conditional join
-- Below, conditional ignore row with name = 'Will' > SELECT
> emp.name, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph ON
> IF(emp.name = 'Will', '1', emp.name) =
> CASE WHEN emph.name = 'Will' THEN '0' ELSE emph.name END; +----------+-----------------+
| emp.name | emph.sin_number |
+----------+-----------------+
| Michael | 547-968-091 |
| Lucy | 577-928-094 |
+----------+-----------------+
2 rows selected (27.191 seconds)
-- Use where/limit to limit the output of join
> SELECT
> emp.name, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph ON emp.name = emph.name
> WHERE
> emp.name = 'Will';
+----------+-----------------+
| emp.name | emph.sin_number |
+----------+-----------------+
| Will | 527-948-090 |
+----------+-----------------+
1 row selected (26.811 seconds)
- 可以对更多具有序列联接的表(如表 A、B 和 C)执行
JOIN
操作。 表可以从 A 连接到 B 和 B 连接到 C,或者从 A 连接到 B 和 A 连接到 C:
> SELECT
> emp.name, empi.employee_id, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph ON emp.name = emph.name
> JOIN employee_id empi ON emp.name = empi.name;
+-----------+-------------------+------------------+
| emp.name | empi.employee_id | emph.sin_number |
+-----------+-------------------+------------------+
| Michael | 100 | 547-968-091 |
| Will | 101 | 527-948-090 |
| Lucy | 103 | 577-928-094 |
+-----------+-------------------+------------------+
3 rows selected (67.933 seconds)
- 自联接是一个表自身联接的地方。 执行此类连接时,应使用不同的别名来区分同一个表:
> SELECT
> emp.name -- Use alias before column name
> FROM employee emp
> JOIN employee emp_b -- Here, use a different alias
> ON emp.name = emp_b.name;
+-----------+
| emp.name |
+-----------+
| Michael |
| Will |
| Shelley |
| Lucy |
+-----------+
4 rows selected (59.891 seconds)
- 在不使用
JOIN
关键字的情况下执行隐式联接。 这仅适用于INNER JOIN
:
> SELECT
> emp.name, emph.sin_number
> FROM
> employee emp, employee_hr emph -- Only applies for inner join
> WHERE
> emp.name = emph.name;
+-----------+------------------+
| emp.name | emph.sin_number |
+-----------+------------------+
| Michael | 547-968-091 |
| Will | 527-948-090 |
| Lucy | 577-928-094 |
+-----------+------------------+
3 rows selected (47.241 seconds)
- 联接条件使用不同的列,这将创建一个额外的作业:
> SELECT
> emp.name, empi.employee_id, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph ON emp.name = emph.name
> JOIN employee_id empi ON emph.employee_id = empi.employee_id;
+-----------+-------------------+------------------+
| emp.name | empi.employee_id | emph.sin_number |
+-----------+-------------------+------------------+
| Michael | 100 | 547-968-091 |
| Will | 101 | 527-948-090 |
| Lucy | 103 | 577-928-094 |
+-----------+-------------------+------------------+
3 rows selected (49.785 seconds)
If JOIN
uses different columns in its conditions, it will request an additional job to complete the join. If the JOIN
operation uses the same column in the join conditions, it will join on this condition using one job.
当在多个表之间执行JOIN
时,将创建 Yarn/MapReduce 作业来处理 HDFS 中的数据。 每一项工作都被称为一个阶段。 通常,建议将大表放在语句的末尾,以获得更好的性能,并避免出现内存不足(OOM)异常。 这是因为JOIN
序列中的最后一个表通常通过还原器流传输,而其他表默认情况下缓冲在还原器中。 此外,还可以指定提示/*+STREAMTABLE (table_name)*/
,以建议应在默认决策上流式传输哪个表,如下例所示:
> SELECT /*+ STREAMTABLE(employee_hr) */
> emp.name, empi.employee_id, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph ON emp.name = emph.name
> JOIN employee_id empi ON emph.employee_id = empi.employee_id;
外部连接
除了INNER JOIN
,HQL 还支持常规的OUTER JOIN
和FULL JOIN
。 这种连接的逻辑与 SQL 中的逻辑相同。 下表总结了常见联接之间的区别。 这里,我们假设table_m
具有m
行,而table_n
具有具有一对一映射的n
行:
联接类型 | 逻辑 | 返回的行数 |
---|---|---|
table_m``JOIN table_n |
这将返回两个表中匹配的所有行。 | m ∩ n |
table_m ``LEFT JOIN table_n |
这将返回左表中的所有行和右表中匹配的行。 如果右表中没有匹配项,则返回右表中的NULL 。 |
m |
table_m ``RIGHT JOIN table_n |
这将返回右表中的所有行和左表中匹配的行。 如果左表中没有匹配项,则返回左表中的NULL 。 |
n |
table_m ``FULL JOIN table_n |
这将返回两个表中的所有行和两个表中匹配的行。 如果左边或右边的表中没有匹配项,则返回NULL 。 |
m + n - m ∩ n |
table_m ``CROSS JOIN table_n |
这将返回两个表中的所有行组合,以生成笛卡尔乘积。 | m * n |
以下示例演示了不同的OUTER JOINs
:
> SELECT
> emp.name, emph.sin_number
> FROM employee emp -- All rows in left table returned
> LEFT JOIN employee_hr emph ON emp.name = emph.name;
+-----------+------------------+
| emp.name | emph.sin_number |
+-----------+------------------+
| Michael | 547-968-091 |
| Will | 527-948-090 |
| Shelley | NULL | -- NULL for mismatch
| Lucy | 577-928-094 |
+-----------+------------------+
4 rows selected (39.637 seconds)
> SELECT
> emp.name, emph.sin_number
> FROM employee emp -- All rows in right table returned
> RIGHT JOIN employee_hr emph ON emp.name = emph.name;
+-----------+------------------+
| emp.name | emph.sin_number |
+-----------+------------------+
| Michael | 547-968-091 |
| Will | 527-948-090 |
| NULL | 647-968-598 | -- NULL for mismatch | Lucy | 577-928-094 |
+-----------+------------------+
4 rows selected (34.485 seconds)
> SELECT
> emp.name, emph.sin_number
> FROM employee emp -- Rows from both side returned
> FULL JOIN employee_hr emph ON emp.name = emph.name;
+-----------+------------------+
| emp.name | emph.sin_number |
+-----------+------------------+
| Lucy | 577-928-094 |
| Michael | 547-968-091 |
| Shelley | NULL | -- NULL for mismatch | NULL | 647-968-598 | -- NULL for mismatch | Will | 527-948-090 |
+-----------+------------------+
5 rows selected (64.251 seconds)
CROSS JOIN
语句没有联接条件。 也可以使用不带条件的联接或始终为真的条件(如 1=1)来编写CROSS JOIN
语句。在这种情况下,我们可以使用交叉联接来联接任何数据集。 然而,我们仅在必须链接本质上没有关系的数据时才考虑使用此类联接,例如向表中添加带有行计数的头。 以下是三种相等的书写方式CROSS JOIN
:
> SELECT
> emp.name, emph.sin_number
> FROM employee emp
> CROSS JOIN employee_hr emph;
> SELECT
> emp.name, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph;
> SELECT
> emp.name, emph.sin_number
> FROM employee emp
> JOIN employee_hr emph on 1=1; +-----------+------------------+
| emp.name | emph.sin_number |
+-----------+------------------+
| Michael | 547-968-091 |
| Michael | 527-948-090 |
| Michael | 647-968-598 |
| Michael | 577-928-094 |
| Will | 547-968-091 |
| Will | 527-948-090 |
| Will | 647-968-598 |
| Will | 577-928-094 |
| Shelley | 547-968-091 |
| Shelley | 527-948-090 |
| Shelley | 647-968-598 |
| Shelley | 577-928-094 |
| Lucy | 547-968-091 |
| Lucy | 527-948-090 |
| Lucy | 647-968-598 |
| Lucy | 577-928-094 |
+-----------+------------------+
16 rows selected (34.924 seconds)
尽管配置单元在早期版本中不明确支持不相等联接,但可以使用CROSS JOIN
和WHERE
解决办法,如下例所示:
> SELECT
> emp.name, emph.sin_number
> FROM employee emp
> CROSS JOIN employee_hr emph
> WHERE emp.name <> emph.name;
+-----------+------------------+
| emp.name | emph.sin_number |
+-----------+------------------+
| Michael | 527-948-090 |
| Michael | 647-968-598 |
| Michael | 577-928-094 |
| Will | 547-968-091 |
| Will | 647-968-598 |
| Will | 577-928-094 |
| Shelley | 547-968-091 |
| Shelley | 527-948-090 |
| Shelley | 647-968-598 |
| Shelley | 577-928-094 |
| Lucy | 547-968-091 | | Lucy | 527-948-090 |
| Lucy | 647-968-598 |
+-----------+------------------+
13 rows selected (35.016 seconds)
特殊连接
HQL 还支持一些我们通常在关系数据库中看不到的特殊连接,例如MapJoin
和Semi-join
。 MapJoin
这意味着只使用 map 执行联接操作,而不使用 Reduce 作业。 MapJoin
语句将所有数据从小表读取到内存,并广播到所有映射。 在映射阶段,通过将大表和小表中的每行数据与连接条件进行比较来执行连接操作。 因为不需要约简,所以这类连接通常具有更好的性能。 在较新版本的配置单元中,如果可能,配置单元会在运行时自动将 Join 转换为MapJoin
。 但是,您也可以通过提供联接提示来手动指定广播表,例如/*+ MAPJOIN(table_name) */
。 此外,由于MapJoin
和WHERE
都是在映射阶段执行的,因此可以将MapJoin
用于不相等连接以提高性能。 以下是将MapJoin
提示与CROSS JOIN
一起使用的示例:
> SELECT
> /*+ MAPJOIN(employee) */ emp.name, emph.sin_number
> FROM employee emp
> CROSS JOIN employee_hr emph
> WHERE emp.name <> emph.name;
MapJoin
操作不支持以下内容:
- 在
UNION ALL
、LATERAL VIEW
、GROUP BY
/JOIN
/SORT BY
/CLUSTER
和BY
/DISTRIBUTE BY
后使用MapJoin
- 在
UNION
、JOIN
和另一个MapJoin
之前使用MapJoin
Bucket MapJoin
是一种特殊类型的MapJoin
,它使用存储桶列(由CREATE TABLE
语句中的CLUSTERED BY
指定的列)作为联接条件。 与regular MapJoin
所做的获取整个表不同,bucket MapJoin
只获取所需的存储桶数据。 要启用bucket MapJoin
,我们需要启用一些设置,并确保存储桶编号是彼此的倍数。 如果使用相同数量的存储桶对两个联接的表进行排序和分桶,则可以执行排序合并联接,而不是在内存中缓存所有小表:
> SET hive.optimize.bucketmapjoin = true;
> SET hive.optimize.bucketmapjoin.sortedmerge = true;
> SET hive.input.format =
> org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;
此外,LEFT SEMI JOIN
语句也是MapJoin
的一种。 它与配置单元的 v0.13.0 后面带有IN
/EXISTS
的子查询相同。 但是,不建议使用它,因为它不是标准 SQL 的一部分:
> SELECT a.name FROM employee a
> LEFT SEMI JOIN employee_id b ON a.name = b.name;
使用 UNION 组合数据
当我们想要将具有相同架构的数据组合在一起时,我们经常使用集合操作。 关系数据库中的常规集合操作有INTERSECT
、MINUS
和UNION
/UNION ALL
。 HQL 仅支持UNION
和UNION ALL
。 它们之间的不同之处在于,UNION ALL
不删除重复行,而UNION
则删除。 此外,所有联合数据必须具有相同的名称和数据类型,否则将执行隐式转换,并可能导致运行时异常。 如果使用ORDER BY
、SORT BY
、CLUSTER BY
、DISTRIBUTE BY
或LIMIT
,则它们将应用于联合之后的整个结果集:
> SELECT a.name as nm FROM employee a
> UNION ALL -- Use column alias to make the same name for union
> SELECT b.name as nm FROM employee_hr b;
+-----------+
| nm |
+-----------+
| Michael |
| Will |
| Shelley |
| Lucy |
| Michael |
| Will |
| Steven |
| Lucy |
+-----------+
8 rows selected (23.919 seconds)
> SELECT a.name as nm FROM employee a
> UNION -- UNION removes duplicated names and slower
> SELECT b.name as nm FROM employee_hr b;
+----------+
| nm |
+----------+
| Lucy |
| Michael |
| Shelley |
| Steven |
| Will |
+----------+
5 rows selected (32.221 seconds)
-- Order by applies to the unioned data
-- When you want to order only one data set,
-- Use order in the subquery
> SELECT a.name as nm FROM employee a
> UNION ALL
> SELECT b.name as nm FROM employee_hr b
> ORDER BY nm;
+----------+
| nm |
+----------+
| Lucy |
| Lucy |
| Michael |
| Michael |
| Shelley |
| Steven |
| Will |
| Will |
+----------+
对于 HQL 还不支持的其他集合操作,比如INTERCEPT
和MINUS
,我们可以使用 JOINS 或 LEFT JOIN 来实现,如下所示:
-- Use join for set intercept
> SELECT a.name
> FROM employee a
> JOIN employee_hr b ON a.name = b.name;
+----------+
| a.name |
+----------+
| Michael |
| Will |
| Lucy |
+----------+
3 rows selected (44.862 seconds)
-- Use left join for set minus
> SELECT a.name
> FROM employee a
> LEFT JOIN employee_hr b ON a.name = b.name
> WHERE b.name IS NULL;
+----------+
| a.name |
+----------+
| Shelley |
+----------+
1 row selected (36.841 seconds)
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,您学习了如何使用SELECT
语句投影所需的数据,并使用WHERE
、LIMIT
、IN
/EXISTS
过滤数据。 然后,我们介绍了将数据集链接在一起的不同联接,以及数据集操作UNION
和UNION ALL
。 读完本章之后,您应该能够将SELECT
语句与不同的WHERE
条件、LIMIT
、DISTINCT
和复杂子查询一起使用。 您应该能够理解并使用不同类型的JOIN
语句来横向链接不同的数据集或垂直链接它们。
在下一章中,我们将讨论 HQL 中的数据交换、排序和转换以及事务的细节。
五、数据操作
操纵数据的能力在大数据分析中至关重要。 操作数据是交换、移动、排序、转换和修改数据的过程。 此技术可用于许多情况,如清理数据、搜索模式、创建趋势等。 HQL 提供各种语句、关键字、运算符和函数来执行数据操作。
在本章中,我们将介绍以下主题:
- 使用
LOAD
、INSERT
、IMPORT
和EXPORT
进行数据交换 - 数据排序
- 功能
- 事务和锁
与负载进行数据交换
为了移动数据,配置单元使用LOAD
语句。 Move这里表示原始数据被移动到目标表/分区,不再存在于原始位置。 LOAD
语句中的LOCAL
关键字指定文件在客户端主机上的位置。 如果未指定LOCAL
关键字,则默认情况下,将从INPATH
之后指定的完整统一资源标识符(URI)(大多数情况下为hdfs path
)或hdfs-site.xml
中定义的fs.default.name
属性的值加载文件。 INPATH
之后的路径可以是相对路径,也可以是绝对路径。 该路径指向要加载的文件或文件夹(指文件夹中的所有文件),但指定的路径中不允许有子文件夹。 如果将数据加载到分区表中,则必须指定分区列。 关键字OVERWRITE
用于决定是否替换目标表/分区中的现有数据。以下是如何将数据从本地或 HDFS 文件移动到表或分区的示例:
- 在内部或外部的表中加载本地数据。 LOAD 语句不可重复,因为要加载的文件已被移动:
> LOAD DATA LOCAL INPATH
> '/home/dayongd/Downloads/employee_hr.txt'
> OVERWRITE INTO TABLE employee_hr;
No rows affected (0.436 seconds)
- 将本地数据加载到分区中:
> LOAD DATA LOCAL INPATH
> '/home/dayongd/Downloads/employee.txt'
> OVERWRITE INTO TABLE employee_partitioned
> PARTITION (year=2018, month=12);
No rows affected (0.772 seconds)
- 使用 URI 将数据从 HDFS 加载到表中:
-- Use default fs path
> LOAD DATA INPATH
> '/tmp/hivedemo/data/employee.txt'
> INTO TABLE employee; -- Without OVERWRITE, it appends data
No rows affected (0.453 seconds)
-- Use full URI
> LOAD DATA INPATH
> 'hdfs://localhost:9000/tmp/hivedemo/data/employee.txt'
> OVERWRITE INTO TABLE employee;
No rows affected (0.297 seconds)
与 INSERT 进行数据交换
要从表/分区中提取数据,可以使用关键字INSERT
。 与其他关系数据库一样,配置单元支持通过从另一个表中选择数据来将数据插入到表中。 这是一种非常常见的 ETL(数据仓库中用于提取、转换和加载的术语)模式,用于从另一个表或数据集中填充现有的或新的表。 HQL 的INSERT
语句与关系数据库的INSERT
具有相同的语法。 然而,HQL 通过支持数据覆盖、多次插入、动态分区插入以及将数据插入到文件中,改进了它的INSERT
语句。 以下是 HQL 中的几个INSERT
语句示例:
- 以下是
SELECT
语句中的常规INSERT
:
-- Check the target table, which is empty.
> SELECT name, work_place FROM employee;
+-------------+-------------------+
|employee.name|employee.work_place|
+-------------+-------------------+
+-------------+-------------------+
No rows selected (0.115 seconds)
-- Populate data from query while "INTO" will append data
> INSERT INTO TABLE employee SELECT * FROM ctas_employee;
No rows affected (31.701 seconds)
-- Verify the data loaded
> SELECT name, work_place FROM employee;
+-------------+----------------------+
|employee.name| employee.work_place |
+-------------+----------------------+
| Michael |["Montreal","Toronto"]|
| Will |["Montreal"] |
| Shelley |["New York"] |
| Lucy |["Vancouver"] |
+-------------+----------------------+
4 rows selected (0.12 seconds)
- 插入具有指定列的表。 对于未指定的列,填充
NULL
。 然而,目前有两个限制。 首先,它只适用于INSERT INTO
,而不适用于INSERT OVERWRITE
。 其次,未指定的列必须是主要的数组数据类型(如不支持数组)。 同样的限制也适用于以下INSERT INTO ... VALUES
语句:
> CREATE TABLE emp_simple( -- Create a test table only has
primary types
> name string,
> work_place string
> );
No rows affected (1.479 seconds)
> INSERT INTO TABLE emp_simple (name) -- Specify which columns to
insert
> SELECT name FROM employee WHERE name = 'Will';
No rows affected (30.701 seconds)
> INSERT INTO TABLE emp_simple VALUES -- Insert constant values
> ('Michael', 'Toronto'),('Lucy', 'Montreal');
No rows affected (18.045 seconds)
> SELECT * FROM emp_simple; -- Verify the data loaded
+---------+------------+
| name | work_place |
+---------+------------+
| Will | NULL | -- NULL when column is not specified
| Michael | Toronto |
| Lucy | Montreal |
+---------+------------+
3 rows selected (0.263 seconds)
- 从 CTE 语句插入数据:
> WITH a as (
> SELECT * FROM ctas_employee
> )
> FROM a
> INSERT OVERWRITE TABLE employee
> SELECT *;
No rows affected (30.1 seconds)
- 通过只扫描源表一次来运行多次插入,以获得更好的性能:
> FROM ctas_employee
> INSERT OVERWRITE TABLE employee
> SELECT *
> INSERT OVERWRITE TABLE employee_internal
> SELECT *
> INSERT OVERWRITE TABLE employee_partitioned
> PARTITION (year=2018, month=9) -- Insert to static partition
> SELECT *
> ;
No rows affected (27.919 seconds)
The INSERT OVERWRITE
statement will replace the data in the target table/partition, while INSERT INTO
will append data.
在将数据插入分区时,我们需要指定分区列。 与指定静态分区值不同,配置单元还支持动态提供分区值。 当需要从数据值动态填充分区时,动态分区非常有用。 默认情况下禁用动态分区,因为不小心插入动态分区可能会意外创建许多分区。*我们必须设置以下属性才能启用动态分区:
> SET hive.exec.dynamic.partition=true;
No rows affected (0.002 seconds)
默认情况下,用户必须至少指定一个静态分区列。 这是为了避免意外覆盖分区。 要禁用此限制,我们可以在插入到动态分区之前将分区模式从默认的strict
模式设置为nonstrict
,如下所示:
> SET hive.exec.dynamic.partition.mode=nonstrict;
No rows affected (0.002 seconds)
-- Partition year, month are determined from data
> INSERT INTO TABLE employee_partitioned
> PARTITION(year, month)
> SELECT name, array('Toronto') as work_place,
> named_struct("gender","Male","age",30) as gender_age,
> map("Python",90) as skills_score,
> map("R&D",array('Developer')) as depart_title,
> year(start_date) as year, month(start_date) as month
> FROM employee_hr eh
> WHERE eh.employee_id = 102;
No rows affected (29.024 seconds)
Complex type constructors are used in the preceding example to create a constant value of a complex data type.
INSERT
还支持将数据写入文件,与LOAD
相比,这是相反的操作。 它通常用于将数据从SELECT
语句提取到本地/hdfs 目录中的文件。 但是,它只支持OVERWRITE
关键字,这意味着我们只能覆盖而不能向数据文件追加数据。 默认情况下,在导出的文件中,列由 CTrl+A分隔,行由换行符分隔。 列分隔符、行分隔符和集合分隔符也可以像在表创建语句中那样被覆盖。 以下是使用INSERT OVERWRITE ... directory
语句将数据导出到文件的几个示例:
- 我们可以使用默认行分隔符插入到本地文件:
> INSERT OVERWRITE LOCAL DIRECTORY '/tmp/output1'
> SELECT * FROM employee;
No rows affected (30.859 seconds)
Many partial files could be created by reducers when doing an insert into a directory. To merge them into one file, we can use the HDFS merge command: hdfs dfs –getmerge <exported_hdfs_folder> <local_folder>
.
- 使用指定的行分隔符插入到本地文件中:
> INSERT OVERWRITE LOCAL DIRECTORY '/tmp/output2'
> ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
> SELECT * FROM employee;
No rows affected (31.937 seconds)
-- Verify the separator
$vi /tmp/output2/000000_0
Michael,Montreal^BToronto,Male^B30,DB^C80,
Product^CDeveloper^DLead
Will,Montreal,Male^B35,Perl^C85,Product^CLead^BTest^CLead
Shelley,New York,Female^B27,Python^C80,Test^CLead^BCOE^CArchitect
Lucy,Vancouver,Female^B57,Sales^C89^BHR^C94,Sales^CLead
- 使用 MULTI-INSERT 语句从同一表导出数据:
> FROM employee
> INSERT OVERWRITE DIRECTORY '/user/dayongd/output3'
> SELECT *
> INSERT OVERWRITE DIRECTORY '/user/dayongd/output4'
> SELECT name ;
No rows affected (25.4 seconds)
Combined HQL and HDFS shell commands, we can extract data to local or remote files with both append and overwrite supported. The hive -e quoted_hql_string
or hive -f <hql_filename>
commands can execute a HQL query or query file. Linux's redirect operators and piping can be used with these commands to redirect result sets. The following are a few examples:
- 附加到本地文件:和
$hive -e 'select * from employee' >> test
- 覆盖本地文件:和
$hive -e 'select * from employee' > test
- 附加到 HDFS 文件:和
$hive -e 'select * from employee'|hdfs dfs -appendToFile - /tmp/test1
- 覆盖 HDFS 文件:
$hive -e 'select * from employee'|hdfs dfs -put -f - /tmp/test2
与[EX|IM]端口进行数据交换
在进行数据迁移或版本部署时,我们可能需要在不同的环境或群集之间移动数据。 在 HQL 中,EXPORT
和IMPORT
语句可用于在不同环境或集群中的 HDF 之间移动数据。 EXPORT
语句从表或分区中导出数据和元数据。 元数据在名为_metadata
的文件中导出。 数据导出到名为 data 的子目录中,如下所示:
> EXPORT TABLE employee TO '/tmp/output5';
No rows affected (0.19 seconds)
> dfs -ls -R /tmp/output5/;
+--------------------------------+
| DFS Output |
+--------------------------------+
| ... /tmp/output5/_metadata |
| ... /tmp/output5/data |
| ... /tmp/output5/data/000000_0 |
+--------------------------------+
3 rows selected (0.014 seconds)
For EXPORT
, the database name can be used before the table name without any syntax error, but the database is useless and ignored by the IMPORT
statement.
导出后,我们可以手动或使用命令hadoop distcp <srcurl> <desturl>
将导出的文件复制到其他群集。 然后,我们可以通过以下方式导入数据:
- 将数据导入到新表中。 如果该表存在,它将抛出一个错误:
> IMPORT TABLE FROM '/tmp/output5'; -- By default, use exported
name
Error: Error while compiling statement: FAILED: SemanticException
[Error 10119]: Table exists and contains data files
(state=42000,code=10119)
> IMPORT TABLE empolyee_imported -- Specify a table imported
> FROM '/tmp/output5';
No rows affected (0.788 seconds)
- 将数据导入外部表,其中
LOCATION
属性是可选的:
> IMPORT EXTERNAL TABLE empolyee_imported_external
> FROM '/tmp/output5'
> LOCATION '/tmp/output6';
No rows affected (0.256 seconds)
- 导出和导入分区:
> EXPORT TABLE employee_partitioned partition
> (year=2018, month=12) TO '/tmp/output7';
No rows affected (0.247 seconds)
> IMPORT TABLE employee_partitioned_imported
> FROM '/tmp/output7';
No rows affected (0.14 seconds)
数据排序
处理数据的另一个方面是对数据进行适当的排序,以便清楚地识别重要事实,例如前N值、最大值、最小值等。 HQL 支持以下关键字进行数据排序:
ORDER BY [ASC|DESC]
:类似于 SQL 语句ORDER BY
。 使用ORDER BY
时,会在每个减速器的所有输出上维护排序顺序。 它只使用一个减法器执行全局排序,因此返回结果需要更长的时间。ORDER BY
之后的方向说明符可以是ASC
,表示上升(从低到高),也可以是DESC
,表示下降(从高到低)。 如果不提供方向说明符,则使用缺省升序。从 v2.1.0 开始,ORDER BY
语句支持指定NULL
值的排序方向,如NULL FIRST
或NULL LAST
。 默认情况下,NULL
在ASC
方向保持在第一位,在DESC
方向保持在最后一位:
> SELECT name FROM employee ORDER BY name DESC; -- By columns
+----------+
| name |
+----------+
| Will |
| Shelley |
| Michael |
| Lucy |
+----------+
4 rows selected (24.057 seconds)
> SELECT name
> FROM employee -- Order by expression
> ORDER BY CASE WHEN name = 'Will' THEN 0 ELSE 1 END DESC;
+----------+
| name |
+----------+
| Lucy |
| Shelley |
| Michael |
| Will |
+----------+
4 rows selected (25.057 seconds)
> SELECT * FROM emp_simple ORDER BY work_place NULL LAST;
+---------+------------+
| name | work_place |
+---------+------------+
| Lucy | Montreal |
| Michael | Toronto |
| Will | NULL | -- NULL stays at the last
+---------+------------+
3 rows selected (0.263 seconds)
Using LIMIT
with ORDER BY
is strongly recommended. When the hive.mapred.mode = strict
property is set(the default value for hive.mapred.mode
is nonstrict in Hive v1.*
and strict in Hive v2.*
), it throws exceptions when using ORDER BY
without LIMIT
*. *
SORT BY [ASC|DESC]
:它指定使用哪些列对减速器输入记录进行排序。 这意味着在将数据发送到减速器之前,分类工作尚未完成。SORT BY
语句不会执行全局排序(但ORDER BY
会执行),并且只确保数据在每个归约器中进行本地排序。 如果SORT BY
仅与一个减速器(setmapred.reduce.tasks=1
)排序,则等于ORDER BY
,,如下例所示。 大多数情况下,SORT BY
本身没有用处,但与下面介绍的DISTRIBUTE BY
、一起使用:
> SET mapred.reduce.tasks = 2; -- Sort by with more than 1 reducer No rows affected (0.001 seconds)
> SELECT name FROM employee SORT BY name DESC;
+---------+
| name |
+---------+
| Shelley | -- Once result is collected to client, it is
| Michael | order-less
| Lucy |
| Will |
+---------+
4 rows selected (54.386 seconds)
> SET mapred.reduce.tasks = 1; -- Sort by one reducer
No rows affected (0.002 seconds)
> SELECT name FROM employee SORT BY name DESC;
+----------+
| name |
+----------+
| Will | -- Same result to ORDER BY
| Shelley |
| Michael |
| Lucy |
+----------+
4 rows selected (46.03 seconds)
DISTRIBUTE BY
:它与GROUP BY
非常相似(在第 6 章,数据聚合和采样)和中介绍,当映射器决定它可以将输出传递到哪个减速器时。 与GROUP BY
相比,DISTRIBUTE BY
不会处理数据聚合,例如count(*)
,而只是指示数据的去向。 在这种情况下,通常使用DISTRIBUTE BY
按指定的列重新组织文件中的数据。 例如,,我们可能需要在一个新的UNION
结果集之后使用和DISTRIBUTE BY
来以更高的粒度重新组织数据。当与SORT BY
和一起使用对指定组内的数据进行排序时,在一个查询中,可以在SORT BY
之前使用DISTRIBUTE BY
。 此外,DISTRIBUTE BY
和之后的列必须出现在选择列列表中,如下所示:
-- Error when not specify distributed column employee_id in
select
> SELECT name FROM employee_hr DISTRIBUTE BY employee_id;
Error: Error while compiling statement: FAILED: SemanticException
[Error 10004]: Line 1:44 Invalid table alias or column reference
'employee_id': (possible column names are: name)
> SELECT name, employee_id FROM employee_hr DISTRIBUTE BY
employee_id;
+----------+--------------+
| name | employee_id |
+----------+--------------+
| Lucy | 103 |
| Steven | 102 |
| Will | 101 |
| Michael | 100 |
+----------+--------------+
4 rows selected (38.92 seconds)
-- Used with SORT BY to order name started on the same day
> SELECT name, start_date
> FROM employee_hr
> DISTRIBUTE BY start_date SORT BY name;
+----------+--------------+
| name | start_date |
+----------+--------------+
| Lucy | 2010-01-03 |
| Michael | 2014-01-29 |
| Steven | 2012-11-03 |
| Will | 2013-10-02 |
+----------+--------------+
4 rows selected (38.01 seconds)
CLUSTER BY
:它是一个快捷运算符,可用于对同一组列执行DISTRIBUTE BY
和SORT BY
操作。CLUSTER BY
语句还不允许您指定ASC
或DESC
。 与全局排序的ORDER BY
相比,CLUSTER BY
语句对每个分布式组中的数据进行排序:
> SELECT name, employee_id FROM employee_hr CLUSTER BY name;
+----------+--------------+
| name | employee_id |
+----------+--------------+
| Lucy | 103 |
| Michael | 100 |
| Steven | 102 |
| Will | 101 |
+----------+--------------+
4 rows selected (39.791 seconds)
When we have to do a global sort, we can do CLUSTER BY
first and then ORDER BY
. In this way, we can fully utilize all the available reducers ahead of ORDER BY
and have better performance, for example: SELECT * FROM (SELECT * FROM employee CLUSTER BY name) base ORDER BY name;
.
总而言之,这些排序关键字之间的区别如下图所示:
HQL sorting keywords difference
功能
为了进一步操作数据,我们还可以使用 HQL 中的运算符、表达式、公式和函数来转换数据。 配置单元维基(https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF)提供了所有支持的表达式和函数的规范,因此除了本章中的几个重要提示外,我们不想在此重复所有这些规范。
HIVE 定义了关系运算符、算术运算符、逻辑运算符、复杂类型运算符和复杂类型运算符构造函数。 对于关系运算符、算术运算符和逻辑运算符,它们类似于 SQL/Java 中的标准运算符。 对于复杂数据类型上的运算符,我们已经在第 3 章、数据定义和描述的了解配置单元数据类型一节中介绍了它们,以及本章前面介绍的将数据插入动态分区的示例。 HQL 中的功能分类如下:
- 数学函数:主要用于进行数学计算,如
rand(...)
、pi(...)
- 集合函数:它们用于查找复杂类型的大小、键和值,如
size(...)
- 类型转换函数:主要是将一种类型转换为另一种类型的
cast(...)
函数和binary(...)
函数 - 日期函数:它们用于执行与日期相关的计算,如
year(...)
和month(...)
- 条件函数:它们用于检查具有定义的返回值的特定条件,如
coalesce(...)
、if(...)
和 Case When Thenelse
End - 字符串函数:它们用于执行与字符串相关的操作,如
upper(...)
和trim(...)
- 聚合函数:它们用于执行聚合(将在下一章中介绍),例如
sum(...)
和和count(*)
- 表格生成函数:这些函数将单个输入行转换为多个输出行,如
explode(...)
和json_tuple(...)
- 定制函数:这些函数由 Java 作为扩展创建,并在章,可扩展性考虑事项中介绍
要列出所有运算符、内置函数和用户定义函数,可以使用以下SHOW FUNCTIONS
命令。 有关特定函数的更多详细信息,我们可以使用DESC [EXTENDED] function_name
,如下所示:
> SHOW FUNCTIONS; -- List all functions
> DESCRIBE FUNCTION <function_name>; -- Detail for the function
> DESCRIBE FUNCTION EXTENDED <function_name>; -- More details
以下是结合示例使用 HQL 函数的一些提示和最佳实践。
集合的函数提示
size(...)
函数用于计算MAP
、ARRAY
或嵌套的MAP/ARRAY
的集合大小。 如果集合为NULL
,则返回-1
,如果集合为空,则返回0
,如下所示:
> SELECT
> SIZE(work_place) as array_size,
> SIZE(skills_score) as map_size,
> SIZE(depart_title) as complex_size,
> SIZE(depart_title["Product"]) as nest_size
> FROM employee;
+-------------+-----------+---------------+------------+
| array_size | map_size | complex_size | nest_size |
+-------------+-----------+---------------+------------+
| 2 | 1 | 1 | 2 |
| 1 | 1 | 2 | 1 |
| 1 | 1 | 2 | -1 |
| 1 | 2 | 1 | -1 |
+-------------+-----------+---------------+------------+
4 rows selected (0.062 seconds)
> SELECT size(null), size(array(null)), size(array());
+-----+-----+-----+
| _c0 | _c1 | _c2 |
+-----+-----+-----+
| -1 | 1 | 0 |
+-----+-----+-----+
1 row selected (11.453 seconds)
array_contains(...)
函数检查数组是否包含一些值,并返回TRUE
或FALSE
。 函数的作用是:按升序对数组进行排序。 这些选项可按如下方式使用:
> SELECT
> array_contains(work_place, 'Toronto') as is_Toronto,
> sort_array(work_place) as sorted_array
> FROM employee;
+-------------+-------------------------+
| is_toronto | sorted_array |
+-------------+-------------------------+
| true | ["Montreal","Toronto"] |
| false | ["Montreal"] |
| false | ["New York"] |
| false | ["Vancouver"] |
+-------------+-------------------------+
4 rows selected (0.059 seconds)
日期和字符串的函数提示
函数的作用是:从日期中删除小时、分钟和秒。 当我们需要检查 Date/Time 类型列的值是否在数据范围内(例如 2014-11-01 和 2014-11-31 之间的to_date(update_datetime)
)时,这很有用。 to_date(...)
的用法如下:
> SELECT TO_DATE(FROM_UNIXTIME(UNIX_TIMESTAMP())) as currentdate;
+---------------+
| currentdate |
+---------------+
| 2018-05-15 |
+---------------+
1 row selected (0.153 seconds)
函数的作用是:颠倒字符串中每个字母的顺序。 函数的作用是:使用指定的标记器对字符串进行标记化。 下面是使用这两个参数从路径中获取文件名的示例:
> SELECT
> reverse(split(reverse('/home/user/employee.txt'),'/')[0])
> as linux_file_name;
+------------------+
| linux_file_name |
+------------------+
| employee.txt |
+------------------+
1 row selected (0.1 seconds)
explode(...)
将数组或映射中的每个元素输出为单独的行,而collect_set(...)
和collect_list(...)
则相反,它们返回每个组的元素集/列表。 collect_set(...)
语句将从结果中删除重复项,但collect_list(...)
不会:
> SELECT
> collect_set(gender_age.gender) as gender_set,
> collect_list(gender_age.gender) as gender_list
> FROM employee;
+-------------------+-----------------------------------+
| gender_set | gender_list |
+-------------------+-----------------------------------+
| ["Male","Female"] | ["Male","Male","Female","Female"] |
+-------------------+-----------------------------------+
1 row selected (24.488 seconds)
虚拟列函数
虚拟列是 HQL 中的特殊功能。 现在,有两个虚拟列:INPUT__FILE__NAME
和BLOCK__OFFSET__INSIDE__FILE
。 函数INPUT__FILE__NAME
显示映射器任务的输入文件名。BLOCK__OFFSET__INSIDE__FILE
函数显示当前全局文件位置或当前块的文件偏移量(如果文件被压缩)。 以下是使用虚拟列找出数据在 HDFS 中的物理位置的示例,特别是对于存储桶表和分区表:
> SELECT
> INPUT__FILE__NAME,BLOCK__OFFSET__INSIDE__FILE as OFFSIDE
> FROM employee;
+-----------------------------------------------------------------------+
| input__file__name | offside |
+-----------------------------------------------------------------------+
| hdfs://localhost:9000/user/hive/warehouse/employee/000000_0 | 0 |
| hdfs://localhost:9000/user/hive/warehouse/employee/000000_0 | 62 |
| hdfs://localhost:9000/user/hive/warehouse/employee/000000_0 | 115 |
| hdfs://localhost:9000/user/hive/warehouse/employee/000000_0 | 176 |
+-------------------------------------------------------------+---------+
4 rows selected (0.47 seconds)
事务和锁
ACID属性(原子性、一致性、数据隔离性和持久性)是人们期待已久的配置单元特性,它为关系数据库奠定了基础;从配置单元 0.14.0 版开始就可以使用了。 配置单元中的完全 ACID 支持是通过行级事务和锁实现的。这使得配置单元能够处理并发读写、数据清理、数据修改、复杂的 ETL/SCD(慢变维度)、流数据摄取、批量数据合并等用例。 在本节中,我们将更详细地介绍它们。
交易记录
目前,HQL 中的所有事务都是自动提交的,不像关系数据库那样支持、BEGIN
、COMMIT
和ROLLBACK
。 此外,启用了事务功能的表必须是具有ORC
文件格式的桶表。 要启用事务支持,必须在hive-site.xml
或直线连接字符串中适当设置以下配置参数:
> SET hive.support.concurrency = true;
> SET hive.enforce.bucketing = true;
> SET hive.exec.dynamic.partition.mode = nonstrict;
> SET hive.txn.manager =
> org.apache.hadoop.hive.ql.lockmgr.DbTxnManager;
> SET hive.compactor.initiator.on = true;
> SET hive.compactor.worker.threads = 1;
When a transaction is enabled, each transaction-related operation, such as INSERT
, UPDATE
, and DELETE
, stores data in delta files. At read time, the reader merges the base and delta files, applying any updates and deletes. Both base and delta directory names contain the transaction IDs. Occasionally, these changes need to be merged into the base files by compactors, which are background processes in the metastore
, for better performance and smaller file size. To see a list of all tables/partitions currently being compacted or scheduled for compaction, use the SHOW COMPACTIONS
statement.
然后,创建一个在表属性中启用了事务的表,并填充数据:
> CREATE TABLE employee_trans (
> emp_id int,
> name string,
> start_date date,
> quit_date date,
> quit_flag string
> )
> CLUSTERED BY (emp_id) INTO 2 BUCKETS STORED as ORC
> TBLPROPERTIES ('transactional'='true'); -- Also need to set this No rows affected (2.216 seconds)
> INSERT INTO TABLE employee_trans VALUES
> (100, 'Michael', '2017-02-01', null, 'N'),
> (101, 'Will', '2017-03-01', null, 'N'),
> (102, 'Steven', '2018-01-01', null, 'N'),
> (104, 'Lucy', '2017-10-01', null, 'N');
No rows affected (48.216 seconds)
对于启用了事务的表,我们可以对数据执行UPDATE
、DELETE
和MERGE
操作。
UPDATE 语句
UPDATE
语句用于在满足特定条件时更新表中的一列或多列。 在这里,更新后的列不能对列或存储桶列进行分区。 用于更新的值应该是表达式或常量,而不是子查询:
> UPDATE employee_trans
> SET quite_date = current_date, quit_flag = 'Y'
> WHERE emp_id = 104;
No rows affected (39.745 seconds)
> SELECT
> quit_date, quit_flag
> FROM employee_trans
> WHERE emp_id = 104; -- Verify the update
+-------------+-----------+
| quit_date | quit_flag |
+-------------+-----------+
| 2018-04-20 | Y |
+-------------+-----------+
1 row selected (0.325 seconds)
DELETE 语句
DELETE
语句用于在满足特定条件时从表中删除一行或多行,如下所示:
> DELETE FROM employee_trans WHERE emp_id = 104;
No rows affected (42.298 seconds)
-- Verify the result, deleted
> SELECT name FROM employee_trans WHERE emp_id = 104;
+------+
| name |
+------+
+------+
No rows selected (0.33 seconds)
MERGE 语句
从配置单元 2.2 开始提供的MERGE
语句用于根据与源表或查询匹配或不匹配的JOIN
条件,对目标表执行UPDATE
、DELETE
或INSERT
操作。 标准语法如下:
MERGE INTO <target_table> as Target USING <source_query/table> as Source
ON <join_condition between two tables>
WHEN MATCHED [AND <boolean expression>] THEN UPDATE SET <set clause list>
WHEN MATCHED [AND <boolean expression>] THEN DELETE
WHEN NOT MATCHED [AND <boolean expression>] THEN INSERT VALUES <value list>
当前对MERGE INTO
语句的限制如下:
- 可能存在一个、两个或三个
WHEN
子句 - 每种类型的
UPDATE
/DELETE
/INSERT
最多只能使用一种 WHEN NOT MATCHED
必须是最后一个子句,并且只支持INSERT VALUES <value_list>
WHEN MATCHED
仅支持UPDATE
或DELETE
- 如果同时存在
UPDATE
和DELETE
子句,则语句中的第一个子句必须包含[AND <boolean expression>]
以下是在 HQL 中合并数据的示例:
-- Create another table as merge source > CREATE TABLE employee_update (
> emp_id int,
> name string,
> start_date date,
> quit_date date,
> quit_flag string
> );
No rows affected (0.127 seconds) -- Populate data > INSERT INTO TABLE employee_update VALUES
> (100, 'Michael', '2017-02-01', '2018-01-01', 'Y'), -- People quit
> (102, 'Steven', '2018-01-02', null, 'N'), -- People has start_date update
> (105, 'Lily', '2018-04-01', null, 'N'); -- People newly started No rows affected (19.832 seconds) -- Do a data merge from employee_update to employee_trans > MERGE INTO employee_trans as tar USING employee_update as src
> ON tar.emp_id = src.emp_id
> WHEN MATCHED and src.quit_flag <> 'Y' THEN UPDATE SET start_date src.start_date
> WHEN MATCHED and src.quit_flag = 'Y' THEN DELETE
> WHEN NOT MATCHED THEN INSERT VALUES (src.emp_id, src.name, src.start_date, src.quit_date, src.quit_flag);
No rows affected (174.357 seconds)
SELECT * FROM employee_trans; -- Verify the result, Michael is deleted
+--------+--------+------------+-----------+-----------+
| emp_id | name |start_date | quit_date | quit_flag |
+--------+--------+------------+-----------+-----------+
| 102 | Steven | 2018-01-02 | NULL | N | -- Update
| 101 | Will | 2017-03-01 | NULL | N |
| 105 | Lily | 2018-04-01 | NULL | N | -- Insert
+--------+--------+------------+-----------+-----------+
3 rows selected (0.356 seconds)
在 HQL 中,可以使用SHOW TRANSACTIONS
语句来显示系统中当前打开和中止的事务。 当我们运行前面的查询时,我们可以打开另一个配置单元连接并发出以下语句来查看当前事务:
> SHOW TRANSACTIONS;
+-----+-----+-------------+-----------------+-------+----------+
|txnid|state|startedtime |lastheartbeattime|user |host |
+-----+-----+-------------+-----------------+-------+----------+
|2 |OPEN |1524183790000|1524183790000 |vagrant|vagrant.vm|
+-----+-----+-------------+-----------------+-------+----------+
2 rows selected (0.063 seconds)
The ABORT TRANSACTIONS transaction_id
statement has been used to kill a transaction with a specified ID since Hive v2.1.0.
锁 / 扣住 / 过船闸 / 隐藏
锁可确保数据隔离,如酸原理所述。 从 v0.7.0 开始,hive 就支持并发访问和锁定机制,并在 v0.13.0 中更新为新的锁管理器。 提供了两种类型的锁,如下所示:
- 共享锁:也称为
S
锁,允许并发共享。 这是在读取表/分区时获取的。 - 排他锁:也称为
X
锁。 这是针对修改表/分区的所有其他操作获取的。
For partition tables, only a shared lock is acquired if the change is only applicable to the newly-created partitions. An exclusive lock is acquired on the table if the change is applicable to all partitions. In addition, an exclusive lock on the table globally affects all partitions. For more information regarding locks, see https://cwiki.apache.org/confluence/display/Hive/Locking.
要启用锁定,请确保在配置单元会话或hive-site.xml
中设置了这两个属性(请参阅上面的事务、节和):
hive.support.concurrency
=true
hive.txn.manager
=org.apache.hadoop.hive.ql.lockmgr.DbTxnManager
任何查询在被允许执行相应的锁允许操作之前都必须获得适当的锁。 当查询为SELECT
时,它将获得一个S
锁。 同一个表上的并发SELECT
语句将获得多个S
锁并并行运行。 当查询为INSERT
时,它将获得一个X
锁。 并发的INSERT
语句将只获得一个X
锁,因此一个INSERT
语句必须等待另一个INSERT
释放锁。 此外,一个表只能有一个X
锁。 当尝试获取X
锁时,表上不应该有其他锁,否则需要X
锁的操作(如INSERT
、ALTER
)必须等待并重试(表hive.lock.sleep.between.retries
属性控制重试时间)。
通过使用新的锁管理器DbTxnManager
,只能从查询隐式获取/释放锁。 要查看表上的锁,请使用SHOW LOCKS
/SHOW LOCKS``table_name
语句:
-- Show all locks when running merge into above > SHOW LOCKS;
+--------+----------+-----------------+------------+------------+
| lockid | database | table | lock_state | lock_type |
+--------+----------+-----------------+------------+------------+
| 19.1 | default | employee_update | ACQUIRED | SHARED_READ|
| 19.2 | default | employee_trans | ACQUIRED |SHARED_WRITE|
+--------+----------+-----------------+-------------------------+
3 rows selected (0.059 seconds)
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,我们介绍了如何使用LOAD
、INSERT
、IMPORT
和EXPORT
关键字在表和文件之间交换数据。 然后,我们介绍了不同的数据排序和排序选项。 我们还介绍了一些使用函数的常用提示。 最后,我们概述了行级事务、DELETE
、UPDATE
、MERGE
和锁。 读完本章后,我们应该能够使用 HQL 导入或导出数据。 我们应该有使用不同类型的数据排序关键字、函数和事务语句的经验。
在下一章中,我们将介绍在 HQL 中执行数据聚合和采样的不同方式。*
六、数据汇总和采样
This chapter is about how to aggregate and sample data in HQL. It first covers the use of several aggregate functions, enhanced aggregate functions, and window functions working with a GROUP BY*,* PARTITION BY statement. Then, it introduces the different ways of sampling data. In this chapter, we will cover the following topics:
- 基本聚合
- 增强型聚合
- 聚集条件
- 窗口函数
- 采样 / 取样 / 抽样 / 样品
基本聚合
数据聚合是根据特定条件收集并以摘要形式表示数据以获得有关特定群体的更多信息的过程。 HQL 提供了几个内置聚合函数,如max(...)
、min(...)
和avg(...)
。 它还支持使用诸如GROUPING SETS
、ROLLUP
和CUBE
等关键字以及不同类型的窗口函数的高级聚合。
基本的内置聚合函数通常与GROUP BY
子句一起使用。 如果没有指定GROUP BY
子句,则默认情况下,它将聚合整个行(所有列)。 除了聚合函数外,GROUP BY
子句中还必须包括所有选定的列。 以下是涉及内置聚合函数的几个示例:
- 不带
GROUP BY
列的数据聚合:
> SELECT
> count(*) as rowcnt1,
> count(1) as rowcnt2 -- same to count(*)
> FROM employee;
+---------+---------+
| rowcnt1 | rowcnt2 |
+---------+---------+
| 4 | 4 |
+---------+---------+
1 row selected (0.184 seconds)
Sometimes, the basic aggregate function call returns the result immediately, such as in the previous example, where it took less than 0.2 seconds. The reason is that Hive fetches such aggregation results directly from the statistics collected (introduced in Chapter 8, Extensibility Considerations). To get the aggregation by actually running a job, you may need to add a limit
or where
clause in the query.
- 包含
GROUP BY
列的聚合:
> SELECT
> gender_age.gender, count(*) as row_cnt
> FROM employee
> GROUP BY gender_age.gender;
+--------------------+----------+
| gender_age.gender | row_cnt |
+--------------------+----------+
| Female | 2 |
| Male | 3 |
+--------------------+----------+
2 rows selected (100.565 seconds)
-- The column name selected is not a group by columns causes error
> SELECT
> name, gender_age.gender, count(*) as row_cnt
> FROM employee GROUP BY gender_age.gender;
Error: Error while compiling statement: FAILED: SemanticException
[Error 10025]: Line 2:1 Expression
not in GROUP BY key 'name' (state=42000,code=10025)
If we have to select columns that are not GROUP BY
columns, one way is to use window functions, which are introduced later.
聚合函数可以与同一SELECT
语句中的其他聚合函数一起使用。 它还可以嵌套的方式与其他函数(如条件函数)一起使用。 但是,不支持嵌套聚合函数。 有关更多详细信息,请参阅以下示例:
- 同一
SELECT
语句中有多个聚合函数:
> SELECT
> gender_age.gender, avg(gender_age.age) as avg_age,
> count(*) as row_cnt
> FROM employee GROUP BY gender_age.gender;
+--------------------+---------------------+----------+
| gender_age.gender | avg_age | row_cnt |
+--------------------+---------------------+----------+
| Female | 42.0 | 2 |
| Male | 31.666666666666668 | 3 |
+--------------------+---------------------+----------+
2 rows selected (98.857 seconds)
- 聚合函数还可以与
CASE WHEN THEN ELSE END
、coalesce(...)
或if(...)
一起使用:
> SELECT
> sum(CASE WHEN gender_age.gender = 'Male'
> THEN gender_age.age ELSE 0 END)/
> count(CASE WHEN gender_age.gender = 'Male' THEN 1
> ELSE NULL END) as male_age_avg
> FROM employee;
+---------------------+
| male_age_avg |
+---------------------+
| 31.666666666666668 |
+---------------------+
1 row selected (38.415 seconds)
> SELECT
> sum(coalesce(gender_age.age,0)) as age_sum,
> sum(if(gender_age.gender = 'Female',gender_age.age,0)) as
female_age_sum
> FROM employee;
+----------+----------------+
| age_sum | female_age_sum |
+----------+----------------+
| 179 | 84 |
+----------+----------------+
1 row selected (42.137 seconds)
GROUP BY
也可以应用于表达式:
> SELECT
> if(name = 'Will', 1, 0) as name_group,
> count(name) as name_cnt
> FROM employee
> GROUP BY if(name = 'Will', 1, 0);
+------------+----------+
| name_group | name_cnt |
+------------+----------+
| 0 | 3 |
| 1 | 1 |
+------------+----------+
2 rows selected (23.749 seconds)
- 验证是否不允许嵌套聚合函数:
> SELECT avg(count(*)) as row_cnt FROM employee;
Error: Error while compiling statement: FAILED: SemanticException
[Error 10128]: Line 1:11 Not yet
supported place for UDAF 'count' (state=42000,code=10128)
- 诸如
max(...)
或min(...)
之类的聚合函数应用于NULL
,并返回NULL
。 但是,像sum()
和avg(...)
这样的函数不能适用于NULL
。count(null)
返回 0。
> SELECT max(null), min(null), count(null);
+------+------+-----+
| _c0 | _c1 | _c2 |
+------+------+-----+
| NULL | NULL | 0 |
+------+------+-----+
1 row selected (23.54 seconds)
> SELECT sum(null), avg(null);
Error: Error while compiling statement: FAILED:
UDFArgumentTypeException Only numeric or string type
arguments are accepted but void is passed.
(state=42000,code=40000)
此外,在处理值为 1NULL
的列之间的聚合时,我们可能会遇到非常特殊的行为。 将忽略整行(如果有一列将NULL
作为该行中的值)。 为了避免这种情况,我们可以在列值为NULL
时使用coalesce(...)
命令分配一个默认值。请参见以下示例:
-- Create a table t for testing
> CREATE TABLE t (val1 int, val2 int);
> INSERT INTO TABLE t VALUES (1, 2),(null,2),(2,3);
No rows affected (0.138 seconds)
-- Check the rows in the table created
> SELECT * FROM t;
+---------+---------+
| t.val1 | t.val2 |
+---------+---------+
| 1 | 2 |
| NULL | 2 |
| 2 | 3 |
+---------+---------+
3 rows selected (0.069 seconds)
-- The 2nd row (NULL, 2) is ignored when doing sum(val1 + val2)
> SELECT sum(val1), sum(val1 + val2) FROM t;
+------+------+
| _c0 | _c1 |
+------+------+
| 3 | 8 |
+------+------+
1 row selected (57.775 seconds)
> SELECT
> sum(coalesce(val1,0)),
> sum(coalesce(val1,0) + val2)
> FROM t;
+------+------+
| _c0 | _c1 |
+------+------+
| 3 | 10 |
+------+------+
1 row selected (69.967 seconds)
- 聚合函数还可以与
DISTINCT
关键字配合使用,以聚合唯一值:
> SELECT
> count(DISTINCT gender_age.gender) as gender_uni_cnt,
> count(DISTINCT name) as name_uni_cnt
> FROM employee;
+-----------------+---------------+
| gender_uni_cnt | name_uni_cnt |
+-----------------+---------------+
| 2 | 5 |
+-----------------+---------------+
1 row selected (35.935 seconds)
当我们同时使用COUNT
和DISTINCT
时,它总是忽略所使用的减速器数量的设置(例如mapred.reduce.tasks = 20
),并且可能只使用一个减速器。 在这种情况下,单个减速器成为处理大量数据的瓶颈。 解决方法是使用子查询,如下所示:
-- May trigger single reducer during the whole processing
> SELECT count(distinct gender_age.gender) as gender_uni_cnt FROM employee;
-- Use subquery to select unique value before aggregations
> SELECT
> count(*) as gender_uni_cnt
> FROM (
> SELECT DISTINCT gender_age.gender FROM employee
) a;
在这种情况下,实现DISTINCT
的查询的第一阶段可以使用多个减法器。 在第二个阶段中,映射器仅用于COUNT
目的的输出将较少,因为在实现DISTINCT
之后数据已经是唯一的。 因此,减速器不会超载。
有时,我们可能需要找到最大值。 或最小。 特定列以及其他列的值,例如,要回答以下问题:员工表中年龄最大的男性和女性是谁?和要实现这一点,我们还可以对结构使用 max/min,如下所示,而不是使用所有子查询/窗口函数:
> SELECT gender_age.gender,
> max(struct(gender_age.age, name)).col1 as age,
> max(struct(gender_age.age, name)).col2 as name
> FROM employee
> GROUP BY gender_age.gender;
+-------------------+-----+------+
| gender_age.gender | age | name |
+-------------------+-----+------+
| Female | 57 | Lucy |
| Male | 35 | Will |
+-------------------+-----+------+
2 rows selected (26.896 seconds)
虽然它仍然需要使用GROUP BY
子句,但该作业比常规的GROUP BY
查询或子查询效率更高,因为它只触发一个作业。
The hive.map.aggr
property controls aggregations in the map
task. The default value for this setting is true
, so Hive will do the first-level aggregation directly in the map
task for better performance, but consume more memory. Turn it off if you run out of memory in the map
phase.
增强型聚合
配置单元通过使用GROUPING SETS
、CUBE
和、ROLLUP
关键字提供增强的聚合。
对集合进行分组
GROUPING SETS
针对同一组数据实现多个高级GROUP BY
操作。 实际上,GROUPING SETS
是将多个GROUP BY
结果集与UNION ALL
连接起来的一种快捷方式。 关键字GROUPING SETS
在作业的单个阶段完成所有流程,效率更高。 GROUPING SETS
子句中的空白集()
计算总体聚合。 下面是几个例子来说明GROUPING SETS
的等价性。 为了更好地理解,我们可以说GROUPING SETS
的外层(大括号)定义了要实现的数据UNION ALL
。 内部级别(大括号)定义要在每个UNION ALL
中实施哪些GROUP BY
数据。
- 具有一个列对元素的分组集:
SELECT
name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name, start_date
GROUPING SETS((name, start_date));
--||-- equals to
SELECT
name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name, start_date;
+---------+------------+---------+
| name | start_date | sin_cnt |
+---------+------------+---------+
| Lucy | 2010-01-03 | 1 |
| Michael | 2014-01-29 | 1 |
| Steven | 2012-11-03 | 1 |
| Will | 2013-10-02 | 1 |
+---------+------------+---------+
4 rows selected (26.3 seconds)
- 包含两个元素的分组集:
SELECT
name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name, start_date
GROUPING SETS(name, start_date);
--||-- equals to SELECT
name, null as start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name
UNION ALL
SELECT
null as name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY start_date;
----------+------------+---------+
| name | start_date | sin_cnt |
+---------+------------+---------+
| NULL | 2010-01-03 | 1 |
| NULL | 2012-11-03 | 1 |
| NULL | 2013-10-02 | 1 |
| NULL | 2014-01-29 | 1 |
| Lucy | NULL | 1 |
| Michael | NULL | 1 |
| Steven | NULL | 1 |
| Will | NULL | 1 |
+---------+------------+---------+
8 rows selected (22.658 seconds)
- 包含两个元素、列对和列的分组集:
SELECT
name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name, start_date
GROUPING SETS((name, start_date), name);
--||-- equals to SELECT
name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name, start_date
UNION ALL
SELECT
name, null as start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name;
+---------+------------+---------+
| name | start_date | sin_cnt |
+---------+------------+---------+
| Lucy | NULL | 1 |
| Lucy | 2010-01-03 | 1 |
| Michael | NULL | 1 |
| Michael | 2014-01-29 | 1 |
| Steven | NULL | 1 |
| Steven | 2012-11-03 | 1 |
| Will | NULL | 1 |
| Will | 2013-10-02 | 1 |
+---------+------------+---------+
8 rows selected (22.503 seconds)
- 包含四个元素的分组集,包括所有列的组合:
SELECT
name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name, start_date
GROUPING SETS((name, start_date), name, start_date, ());
--||-- equals to SELECT
name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name, start_date
UNION ALL
SELECT
name, null as start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY name
UNION ALL
SELECT
null as name, start_date, count(sin_number) as sin_cnt
FROM employee_hr
GROUP BY start_date
UNION ALL
SELECT
null as name, null as start_date, count(sin_number) as sin_cnt
FROM employee_hr
+---------+------------+---------+
| name | start_date | sin_cnt |
+---------+------------+---------+
| NULL | NULL | 4 |
| NULL | 2010-01-03 | 1 |
| NULL | 2012-11-03 | 1 |
| NULL | 2013-10-02 | 1 |
| NULL | 2014-01-29 | 1 |
| Lucy | NULL | 1 |
| Lucy | 2010-01-03 | 1 |
| Michael | NULL | 1 |
| Michael | 2014-01-29 | 1 |
| Steven | NULL | 1 |
| Steven | 2012-11-03 | 1 |
| Will | NULL | 1 |
| Will | 2013-10-02 | 1 |
+---------+------------+---------+
13 rows selected (24.916 seconds)
汇总和多维数据集
ROLLUP
语句使SELECT
语句能够计算指定维度组中的多个级别的聚合。 ROLLUP
语句是GROUP BY
子句的简单扩展,具有很高的效率和最小的查询开销。 与创建指定级别的聚合的GROUPING SETS
相比,ROLLUP
创建了n+1级别,其中n是分组列数。 首先,它计算在GROUP BY
子句中指定的标准聚合值。 然后,它创建更高级别的小计,在分组列的组合列表中从右向左移动。 例如,GROUP BY a,b,c WITH ROLLUP
和等同于GROUP BY a,b,c GROUPING SETS ((a,b,c),(a,b),(a),())
。
CUBE
语句接受一组指定的分组列,并为它们的所有可能组合创建聚合。 如果为多维数据集指定了n列,则将返回 2 个n聚合组合。 例如,GROUP BY a,b,c WITH CUBE
和等同于GROUP BY a,b,c GROUPING SETS ((a,b,c),(a,b),(b,c),(a,c),(a),(b),(c),())
。
函数GROUPING__ID
和可以作为一个扩展来区分整行。 它返回在GROUP BY
之后指定的每一列的位向量的十进制等效值。 返回的十进制数是从 1 和 0 的二进制转换而来的,它表示列是否在行中聚合(0)。另一方面,函数还通过直接返回二进制 1 或 0 来指示是否聚合GROUP BY
子句中的列。 在下面的示例中,列的顺序从从GROUP BY
开始计算最近的列(如name
)开始。 结果集中的第一行表示GROUP BY
中没有使用任何列。
将以下示例与GROUPING SETS
部分中的最后一个示例进行比较,以更好地理解GROUPING_ID
和grouping(...)
:
SELECT
name, start_date, count(employee_id) as emp_id_cnt,
GROUPING__ID,
grouping(name) as gp_name,
grouping(start_date) as gp_sd
FROM employee_hr
GROUP BY name, start_date
WITH CUBE ORDER BY name, start_date;
+---------+------------+------------+-----+---------+-------+
| name | start_date | emp_id_cnt | gid | gp_name | gp_sd |
+---------+------------+------------+-----+---------+-------+
| NULL | NULL | 4 | 3 | 1 | 1 |
| NULL | 2010-01-03 | 1 | 2 | 1 | 0 |
| NULL | 2012-11-03 | 1 | 2 | 1 | 0 |
| NULL | 2013-10-02 | 1 | 2 | 1 | 0 |
| NULL | 2014-01-29 | 1 | 2 | 1 | 0 |
| Lucy | NULL | 1 | 1 | 0 | 1 |
| Lucy | 2010-01-03 | 1 | 0 | 0 | 0 |
| Michael | NULL | 1 | 1 | 0 | 1 |
| Michael | 2014-01-29 | 1 | 0 | 0 | 0 |
| Steven | NULL | 1 | 1 | 0 | 1 |
| Steven | 2012-11-03 | 1 | 0 | 0 | 0 |
| Will | NULL | 1 | 1 | 0 | 1 |
| Will | 2013-10-02 | 1 | 0 | 0 | 0 |
+---------+------------+------------+-----+---------+-------+
13 rows selected (55.507 seconds)
聚集条件
从 v0.7.0 开始,增加了HAVING
,直接支持聚合结果的条件过滤。 通过使用HAVING
,我们可以避免在GROUP BY
语句之后使用子查询。 请参见以下示例:
> SELECT
> gender_age.age
> FROM employee
> GROUP BY gender_age.age
> HAVING count(*)=1;
+----------------+
| gender_age.age |
+----------------+
| 27 |
| 30 |
| 35 |
| 57 |
+----------------+
4 rows selected (25.829 seconds)
> SELECT
> gender_age.age,
> count(*) as cnt -- Support use column alias in HAVING, like ORDER BY
> FROM employee
> GROUP BY gender_age.age HAVING cnt=1;
+----------------+-----+
| gender_age.age | cnt |
+----------------+-----+
| 27 | 1 |
| 30 | 1 |
| 35 | 1 |
| 57 | 1 |
+----------------+-----+
4 rows selected (25.804 seconds)
HAVING
supports filtering on regular columns too. However, it is recommended to use such a filter type after a WHERE
clause rather than HAVING
for better performance.
如果不使用HAVING
,则可以改用子查询,如下所示:
> SELECT
> a.age
> FROM (
> SELECT count(*) as cnt, gender_age.age
> FROM employee GROUP BY gender_age.age
> ) a WHERE a.cnt <= 1;
+--------+
| a.age |
+--------+
| 57 |
| 27 |
| 35 |
+--------+
3 rows selected (87.298 seconds)
窗口函数
窗口函数从配置单元 v0.11.0 开始提供,是扫描多个输入行以计算每个输出值的一组特殊函数。 窗口函数通常与OVER
、PARTITION BY
、ORDER BY
和窗口规范一起使用。 与GROUP BY
子句使用的常规聚合函数不同,并且限制为每组一个结果值,窗口函数在窗口上操作,其中输入行使用通过OVER
和PARTITION
子句表示的灵活条件进行排序和分组。 窗口函数提供聚合结果,但不对结果集进行分组。 它们为每条记录多次返回组值。 与常规的GROUP BY
子句相比,窗口函数提供了极大的灵活性和功能,并使 HQL 进行的特殊聚合更容易、更强大。 窗口函数的语法如下:
Function (arg1,..., argn) OVER ([PARTITION BY <...>] [ORDER BY <....>] [<window_expression>])
Function (arg1,..., argn)
可以是以下四个类别中的任何函数:
- 聚合函数:常规聚合函数,如
sum(...)
和max(...)
- 排序函数:用于对数据进行排序的函数,如
rank(...)
和row_number(...)
- 分析函数:用于统计和比较的函数,如
lead(...)
、lag(...)
和first_value(...)
OVER ``[PARTITION BY <...>]
和子句类似于GROUP BY
子句。 它按列将行划分为在一个或多个分区中包含相同值的组。 这些逻辑组称为分区,这与用于分区表的术语不同。 省略PARTITION BY
语句会将操作应用于表中的所有行。
[ORDER BY <....>]
子句与常规ORDER BY
子句相同。 它确保由PARTITION BY
子句生成的行按规范排序,例如升序或降序。
接下来,我们将通过示例了解每类窗口函数的更多细节。
窗口聚合函数
在窗口函数中使用常规聚合函数比GROUP BY
更灵活,后者需要选择列表中的所有分组列。 从配置单元 v2.2.0 开始,支持将DISTINCT
与窗口函数中的聚合函数一起使用:
- 准备用于演示的表格和数据:
> CREATE TABLE IF NOT EXISTS employee_contract (
> name string,
> dept_num int,
> employee_id int,
> salary int,
> type string,
> start_date date
> )
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '|'
> STORED as TEXTFILE;
No rows affected (0.282 seconds)
> LOAD DATD INPATH '/tmp/hivedemo/data/employee_contract.txt'
> OVERWRITE INTO TABLE employee_contract;
No rows affected (0.48 seconds)
- 常规聚合用作窗口函数:
> SELECT
> name,
> dept_num as deptno,
> salary,
> count(*) OVER (PARTITION BY dept_num) as cnt,
> count(distinct dept_num) OVER (PARTITION BY dept_num) as dcnt,
> sum(salary) OVER(PARTITION BY dept_num ORDER BY dept_num) as
sum1,
> sum(salary) OVER(ORDER BY dept_num) as sum2,
> sum(salary) OVER(ORDER BY dept_num, name) as sum3
> FROM employee_contract
> ORDER BY deptno, name;
+---------+--------+--------+-----+-----+-------+-------+-------+
| name | deptno | salary | cnt | dcnt| sum1 | sum2 | sum3 |
+---------+--------+--------+-----+-----+-------+-------+-------+
| Lucy | 1000 | 5500 | 5 | 1 | 24900 | 24900 | 5500 |
| Michael | 1000 | 5000 | 5 | 1 | 24900 | 24900 | 10500 |
| Steven | 1000 | 6400 | 5 | 1 | 24900 | 24900 | 16900 |
| Wendy | 1000 | 4000 | 5 | 1 | 24900 | 24900 | 20900 |
| Will | 1000 | 4000 | 5 | 1 | 24900 | 24900 | 24900 |
| Jess | 1001 | 6000 | 3 | 1 | 17400 | 42300 | 30900 |
| Lily | 1001 | 5000 | 3 | 1 | 17400 | 42300 | 35900 |
| Mike | 1001 | 6400 | 3 | 1 | 17400 | 42300 | 42300 |
| Richard | 1002 | 8000 | 3 | 1 | 20500 | 62800 | 50300 |
| Wei | 1002 | 7000 | 3 | 1 | 20500 | 62800 | 57300 |
| Yun | 1002 | 5500 | 3 | 1 | 20500 | 62800 | 62800 |
+---------+--------+--------+-----+-----+-------+-------+-------+
11 rows selected (111.856 seconds)
窗口排序函数
窗口排序函数将特定组内的排序数据信息(如行号和排名)作为返回数据的一部分提供。 最常用的排序函数如下:
row_number
:根据分区和顺序规范,为每行分配一个从 1 开始的唯一序列号。rank
:对组中的项目进行排名,例如查找满足特定条件的前三行N。dense_rank
:类似于rank
,但在有平局时不会在排名顺序中留下空白。 例如,如果我们使用dense_rank
来对一场比赛进行排名,并且有两名球员并列第二名,我们会看到这两名球员都排在第二位,下一个人排在第三位。 然而,第二个rank
函数将把两个人排在第二位,但下一个人将排在第四位。percent_rank
:使用排名值而不是其分子中的行数作为(当前排名-1)/(总行数-1)。 因此,它返回值相对于一组值的百分比排名。ntile
:将有序数据集数据集划分为多个存储桶,并为每行分配适当的存储桶编号。 它可用于将行划分为相等的集合,并为每行分配一个数字。
以下是在 HQL 中使用窗口排序函数的一些示例:
> SELECT
> name,
> dept_num as deptno,
> salary,
> row_number() OVER () as rnum, *-- sequence in orginal table*
> rank() OVER (PARTITION BY dept_num ORDER BY salary) as rk,
> dense_rank() OVER (PARTITION BY dept_num ORDER BY salary) as drk,
> percent_rank() OVER(PARTITION BY dept_num ORDER BY salary) as prk,
> ntile(4) OVER(PARTITION BY dept_num ORDER BY salary) as ntile
> FROM employee_contract
> ORDER BY deptno, name;
+---------+--------+--------+------+----+-----+------+-------+
| name | deptno | salary | rnum | rk | drk | prk | ntile |
+---------+--------+--------+------+----+-----+------+-------+
| Lucy | 1000 | 5500 | 7 | 4 | 3 | 0.75 | 3 |
| Michael | 1000 | 5000 | 11 | 3 | 2 | 0.5 | 2 |
| Steven | 1000 | 6400 | 8 | 5 | 4 | 1.0 | 4 |
| Wendy | 1000 | 4000 | 9 | 1 | 1 | 0.0 | 1 |
| Will | 1000 | 4000 | 10 | 1 | 1 | 0.0 | 1 |
| Jess | 1001 | 6000 | 5 | 2 | 2 | 0.5 | 2 |
| Lily | 1001 | 5000 | 6 | 1 | 1 | 0.0 | 1 |
| Mike | 1001 | 6400 | 4 | 3 | 3 | 1.0 | 3 |
| Richard | 1002 | 8000 | 1 | 3 | 3 | 1.0 | 3 |
| Wei | 1002 | 7000 | 3 | 2 | 2 | 0.5 | 2 |
| Yun | 1002 | 5500 | 2 | 1 | 1 | 0.0 | 1 |
+---------+--------+--------+------+----+-----+------+-------+
11 rows selected (80.052 seconds)
从配置单元 v2.1.0 开始,我们可以在OVER
语句中使用聚合函数,如下所示:
> SELECT
> dept_num,
> rank() OVER (PARTITION BY dept_num ORDER BY sum(salary)) as rk
> FROM employee_contract
> GROUP BY dept_num;
+----------+----+
| dept_num | rk |
+----------+----+
| 1000 | 1 |
| 1001 | 1 |
| 1002 | 1 |
+----------+----+
3 rows selected (54.43 seconds)
窗口分析功能
窗口分析函数提供扩展的数据分析,例如获取有序集合中的滞后、领先、最后或第一行。 最常用的分析函数如下:
cume_dist
:计算其值小于或等于总行数除以当前行的值的行数,例如(当前行的行数)/(总行数)。lead
:此函数lead(value_expr[,offset[,default]])
用于返回下一行的数据。 可以选择指定行数(偏移量),默认情况下为 1 行。 当未指定默认值时,该函数返回[,default]
或NULL
。 此外,当前行的引线延伸到窗口末尾之外。lag
:此函数lag(value_expr[,offset[,default]])
用于访问前一行中的数据。 可以选择指定要滞后的行数(偏移量),默认情况下为 1。 当未指定默认值时,该函数返回[,default]
或NULL
。 此外,当前行的滞后时间会延伸到窗口末尾之外。first_value
:它返回有序集的第一个结果。last_value
:它返回有序集的最后一个结果。
以下是在 HQL 中使用窗口分析函数的一些示例:
> SELECT
> name,
> dept_num as deptno,
> salary,
> cume_dist() OVER (PARTITION BY dept_num ORDER BY salary) as cume,
> lead(salary, 2) OVER (PARTITION BY dept_num ORDER BY salary) as lead,
> lag(salary, 2, 0) OVER (PARTITION BY dept_num ORDER BY salary) as lag,
> first_value(salary) OVER (PARTITION BY dept_num ORDER BY salary) as fval,
> last_value(salary) OVER (PARTITION BY dept_num ORDER BY salary) as lval,
> last_value(salary) OVER (PARTITION BY dept_num ORDER BY salary RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as lval2
> FROM employee_contract
> ORDER BY deptno, salary;
+--------+------+--------+------+------+-----+------+------+-------+
| name |deptno| salary | cume | lead | lag | fval |lvalue|lvalue2|
+--------+------+--------+------+------+-----+------+------+-------+
| Will | 1000 | 4000 | 0.4 | 5500 | 0 | 4000 | 4000 | 6400 |
| Wendy | 1000 | 4000 | 0.4 | 5000 | 0 | 4000 | 4000 | 6400 |
| Michael| 1000 | 5000 | 0.6 | 6400 | 4000| 4000 | 5000 | 6400 |
| Lucy | 1000 | 5500 | 0.8 | NULL | 4000| 4000 | 5500 | 6400 |
| Steven | 1000 | 6400 | 1.0 | NULL | 5000| 4000 | 6400 | 6400 |
| Lily | 1001 | 5000 | 0.33 | 6400 | 0 | 5000 | 5000 | 6400 |
| Jess | 1001 | 6000 | 0.67 | NULL | 0 | 5000 | 6000 | 6400 |
| Mike | 1001 | 6400 | 1.0 | NULL | 5000| 5000 | 6400 | 6400 |
| Yun | 1002 | 5500 | 0.33 | 8000 | 0 | 5500 | 5500 | 8000 |
| Wei | 1002 | 7000 | 0.67 | NULL | 0 | 5500 | 7000 | 8000 |
| Richard| 1002 | 8000 | 1.0 | NULL | 5500| 5500 | 8000 | 8000 |
+--------+------+--------+------+------+-----+------+------+-------+
11 rows selected (55.203 seconds)
对于last_value
,结果(lval
列)有点出乎意料。 这是因为使用的默认 WINDOW 子句(将在下一节中介绍)是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
,在本例中,这意味着当前行将始终是最后一个值。 将窗口子句更改为RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
会给出预期的结果(请参阅lval2
列)。
窗口表达式
[<window_expression>]
用于进一步细分结果并应用窗函数。 有两种类型的窗口:行类型窗口和范围类型窗口。
根据https://issues.apache.org/jira/browse/HIVE-4797处的 JIRA,rank(...)
、ntile(...)
、dense_rank(...)
、cume_dist(...)
、percent_rank(...)
、lead(...)
、lag(...)
和row_number(...)
函数还不支持与窗口表达式一起使用。
对于行类型窗口,定义是根据当前行之前或之后的行号。 ROW WINDOW 子句的常规语法如下:
ROWS BETWEEN <start_expr> AND <end_expr>
<start_expr>
可以是以下任一项:
UNBOUNDED PRECEDING
CURRENT ROW
N PRECEDING or FOLLOWING
<end_expr>
可以是以下任一项:
UNBOUNDED FOLLOWING
CURRENT ROW
N PRECEDING or FOLLOWING
下面介绍有关使用窗口表达式及其组合的更多详细信息:
BETWEEN
...AND
:使用它指定窗的起点和终点。 第一个表达式(在AND
之前)定义起点,第二个表达式(在AND
之后)定义终点。 如果我们省略了BETWEEN...AND
(例如ROWS N PRECEDING
或ROWS UNBOUNDED PRECEDING
),则配置单元会将其视为起点,而终结点则默认为当前行(请参阅以下示例中的win6
和win7
列)。N PRECEDING or FOLLOWING
:这表示当前行之前或之后的N行。UNBOUNDED PRECEDING
:这表示窗口从分区的第一行开始。 这是起点规范,不能用作终点规范。UNBOUNDED FOLLOWING
:这表示窗口在分区的最后一行结束。 这是终结点规范,不能用作起点规范。UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
:这表示每行的第一行和最后一行,表示表中的所有行(请参见下面示例中的win14
列)。CURRENT ROW
:作为起点,CURRENT ROW
指定窗口从当前行或值开始,具体取决于我们指定的是ROW
还是RANGE
(范围将在本章后面介绍)。 在这种情况下,端点不能为M PRECEDING
。 作为端点,CURRENT ROW
指定窗口在当前行或当前值结束,具体取决于我们指定的是ROW
还是RANGE
。 在这种情况下,起点不能是N FOLLOWING
。
下面的图表可以帮助我们更清楚地理解前面的定义:
Window expression definitions
以下示例实现 ROW 类型的窗口表达式:
-- Preceding and Following
> SELECT
> name, dept_num as dno, salary as sal,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) win1,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING) win2,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING) win3,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) win4,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) win5,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS 2 PRECEDING) win6, -- FOLLOWING does not work in this way> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS UNBOUNDED PRECEDING) win7
> FROM employee_contract
> ORDER BY dno, name;
+---------+------+------+------+------+------+------+------+------+------+
| name | dno | sal | win1 | win2 | win3 | win4 | win5 | win6 | win7 |
+---------+------+------+------+------+------+------+------+------+------+
| Lucy | 1000 | 5500 | 5500 | 6400 | 6400 | NULL | 6400 | 5500 | 5500 |
| Michael | 1000 | 5000 | 5500 | 6400 | 6400 | 5500 | 6400 | 5500 | 5500 |
| Steven | 1000 | 6400 | 6400 | 6400 | 6400 | 5500 | 4000 | 6400 | 6400 |
| Wendy | 1000 | 4000 | 6400 | 6400 | 6400 | 6400 | 4000 | 6400 | 6400 |
| Will | 1000 | 4000 | 6400 | 6400 | 4000 | 6400 | NULL | 6400 | 6400 |
| Jess | 1001 | 6000 | 6000 | 6400 | 6400 | NULL | 6400 | 6000 | 6000 |
| Lily | 1001 | 5000 | 6000 | 6400 | 6400 | 6000 | 6400 | 6000 | 6000 |
| Mike | 1001 | 6400 | 6400 | 6400 | 6400 | 6000 | NULL | 6400 | 6400 |
| Richard | 1002 | 8000 | 8000 | 8000 | 8000 | NULL | 7000 | 8000 | 8000 |
| Wei | 1002 | 7000 | 8000 | 8000 | 8000 | 8000 | 5500 | 8000 | 8000 |
| Yun | 1002 | 5500 | 8000 | 8000 | 7000 | 8000 | NULL | 8000 | 8000 |
+---------+------+------+------+------+------+------+------+------+------+
11 rows selected (55.885 seconds)
-- Current and Unbounded
> SELECT
> name, dept_num as dno, salary as sal,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN CURRENT ROW AND CURRENT ROW) win8,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) win9,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) win10,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) win11,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) win12,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) win13,
> max(salary) OVER (PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) win14
> FROM employee_contract
> ORDER BY dno, name;
+-------+----+------+------+------+-------+-------+-------+-------+-------+
|name |dno | sal | win8 | win9 | win10 | win11 | win12 | win13 | win14 |
+-------+----+------+------+------+-------+-------+-------+-------+-------+
|Lucy |1000| 5500 | 5500 | 5500 | 6400 | NULL | 5500 | 5500 | 6400 |
|Michael|1000| 5000 | 5000 | 6400 | 6400 | 5500 | 5500 | 6400 | 6400 |
|Steven |1000| 6400 | 6400 | 6400 | 6400 | 5500 | 6400 | 6400 | 6400 |
|Wendy |1000| 4000 | 4000 | 4000 | 4000 | 6400 | 6400 | 6400 | 6400 |
|Will |1000| 4000 | 4000 | 4000 | 4000 | 6400 | 6400 | 6400 | 6400 |
|Jess |1001| 6000 | 6000 | 6000 | 6400 | NULL | 6000 | 6000 | 6400 |
|Lily |1001| 5000 | 5000 | 6400 | 6400 | 6000 | 6000 | 6400 | 6400 |
|Mike |1001| 6400 | 6400 | 6400 | 6400 | 6000 | 6400 | 6400 | 6400 |
|Richard|1002| 8000 | 8000 | 8000 | 8000 | NULL | 8000 | 8000 | 8000 |
|Wei |1002| 7000 | 7000 | 7000 | 7000 | 8000 | 8000 | 8000 | 8000 |
|Yun |1002| 5500 | 5500 | 5500 | 5500 | 8000 | 8000 | 8000 | 8000 |
+-------+----+------+------+------+-------+-------+-------+-------+-------+
11 rows selected (53.754 seconds)
此外,窗口可以在单独的 WINDOW 子句中定义,也可以由其他窗口引用,如下所示:
> SELECT
> name, dept_num, salary,
> max(salary) OVER w1 as win1,
> max(salary) OVER w2 as win2,
> max(salary) OVER w3 as win3
> FROM employee_contract
> WINDOW w1 as (
> PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
> ),
> w2 as w3,
> w3 as (
> PARTITION BY dept_num ORDER BY name
> ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING
> );
+---------+----------+--------+------+------+------+
| name | dept_num | salary | win1 | win2 | win3 |
+---------+----------+--------+------+------+------+
| Lucy | 1000 | 5500 | 5500 | 6400 | 6400 |
| Michael | 1000 | 5000 | 5500 | 6400 | 6400 |
| Steven | 1000 | 6400 | 6400 | 6400 | 6400 |
| Wendy | 1000 | 4000 | 6400 | 6400 | 6400 |
| Will | 1000 | 4000 | 6400 | 4000 | 4000 |
| Jess | 1001 | 6000 | 6000 | 6400 | 6400 |
| Lily | 1001 | 5000 | 6000 | 6400 | 6400 |
| Mike | 1001 | 6400 | 6400 | 6400 | 6400 |
| Richard | 1002 | 8000 | 8000 | 8000 | 8000 |
| Wei | 1002 | 7000 | 8000 | 8000 | 8000 |
| Yun | 1002 | 5500 | 8000 | 7000 | 7000 |
+---------+----------+--------+------+------+------+
11 rows selected (57.204 seconds)
与行类型窗口(以行为单位)相比,范围类型窗口以窗口表达式指定范围内的值为单位。 例如,max(salary) RANGE BETWEEN 500 PRECEDING AND 1000 FOLLOWING
语句将不会根据当前行值-500 到+1000 之间的距离计算分区范围内的max(salary)
。 如果当前行的工资为 4,000 美元,则此max(salary)
将在每个dept_num
指定的分区中包括其工资范围从 3,500 美元到 5,000 美元的行:
> SELECT
> dept_num, start_date, name, salary,
> max(salary) OVER (PARTITION BY dept_num ORDER BY salary
> RANGE BETWEEN 500 PRECEDING AND 1000 FOLLOWING) win1,
> max(salary) OVER (PARTITION BY dept_num ORDER BY salary
> RANGE BETWEEN 500 PRECEDING AND CURRENT ROW) win2
> FROM employee_contract
> order by dept_num, start_date;
+----------+------------+---------+--------+------+------+
| dept_num | start_date | name | salary | win1 | win2 |
+----------+------------+---------+--------+------+------+
| 1000 | 2010-01-03 | Lucy | 5500 | 6400 | 5500 |
| 1000 | 2012-11-03 | Steven | 6400 | 6400 | 6400 |
| 1000 | 2013-10-02 | Will | 4000 | 5000 | 4000 |
| 1000 | 2014-01-29 | Michael | 5000 | 5500 | 5000 |
| 1000 | 2014-10-02 | Wendy | 4000 | 5000 | 4000 |
| 1001 | 2013-11-03 | Mike | 6400 | 6400 | 6400 |
| 1001 | 2014-11-29 | Lily | 5000 | 6000 | 5000 |
| 1001 | 2014-12-02 | Jess | 6000 | 6400 | 6000 |
| 1002 | 2010-04-03 | Wei | 7000 | 8000 | 7000 |
| 1002 | 2013-09-01 | Richard | 8000 | 8000 | 8000 |
| 1002 | 2014-01-29 | Yun | 5500 | 5500 | 5500 |
+----------+------------+---------+--------+------+------+
11 rows selected (60.784 seconds)
如果完全省略Window表达式子句,则缺省窗口规范为RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
。如果同时缺少ORDER BY
和WINDOW
表达式子句,则窗口规范缺省为ROW BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
。
采样 / 取样 / 抽样 / 样品
当数据量特别大时,我们可能需要找到数据子集来加快数据分析。 这就是抽样,这是一种用于识别和分析数据子集以发现整个数据集中的模式和趋势的技术。 在 HQL 中,数据抽样有三种方式:随机抽样、桶表抽样和分块抽样。
随机抽样
随机采样使用rand()
函数和LIMIT
关键字来获取数据采样,如下例所示。 这里使用DISTRIBUTE
和SORT
关键字,以确保数据也在映射器和减法器之间有效地随机分布。 ORDER BY rand()
语句也可以达到同样的目的,但性能不好:
> SELECT name FROM employee_hr
> DISTRIBUTE BY rand() SORT BY rand() LIMIT 2;
+--------+
| name |
+--------+
| Will |
| Steven |
+--------+
2 rows selected (52.399 seconds)
桶表抽样
这是一种特殊的采样方法,针对存储桶表进行了优化,如下例所示。 SELECT
子句指定要从中采样数据的列。 在对整行进行采样时,也可以使用rand()
函数。 如果样本柱也是CLUSTERED BY
柱,则样本柱的效率会更高:
-- Sampling based on the whole row
> SELECT name FROM employee_trans
> TABLESAMPLE(BUCKET 1 OUT OF 2 ON rand()) a;
+--------+
| name |
+--------+
| Steven |
+--------+
1 row selected (0.129 seconds)
-- Sampling based on the bucket column, which is efficient
> SELECT name FROM employee_trans
> TABLESAMPLE(BUCKET 1 OUT OF 2 ON emp_id) a;
+---------+
| name |
+---------+
| Lucy |
| Steven |
| Michael |
+---------+
3 rows selected (0.136 seconds)
整组抽样
这种类型的采样允许查询随机选取n行数据、n百分比的数据大小或n字节的数据。 采样粒度为 HDFS 块大小。 请参考以下示例:
-- Sample by number of rows
> SELECT name
> FROM employee TABLESAMPLE(1 ROWS) a;
+----------+
| name |
+----------+
| Michael |
+----------+
1 rows selected (0.075 seconds)
-- Sample by percentage of data size
> SELECT name
> FROM employee TABLESAMPLE(50 PERCENT) a;
+----------+
| name |
+----------+
| Michael |
| Will |
+----------+
2 rows selected (0.041 seconds)
-- Sample by data size
-- Support b/B, k/K, m/M, g/G
> SELECT name FROM employee TABLESAMPLE(1B) a;
+----------+
| name |
+----------+
| Michael |
+----------+
1 rows selected (0.075 seconds)
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,我们介绍了如何使用基本聚合函数聚合数据。 然后,我们介绍了使用GROUPING SETS
、ROLLUP
和CUBE
的高级聚合,以及使用HAVING
的聚合条件。 我们还介绍了各种窗口函数。 在本章的最后,我们介绍了数据采集的三种方式。 读完本章之后,您应该能够在 HQL 中进行基本的和高级的聚合和数据采样。 在下一章中,我们将讨论 Hive 中的性能考虑因素。
七、性能注意事项
虽然 Hive 是为处理大数据而建造的,但我们仍然不能忽视性能的重要性。 大多数情况下,更好的查询可以依靠智能查询优化器来查找最佳执行策略以及默认设置和最佳实践。 但是,有经验的用户应该更多地了解性能调优的理论和实践,特别是在处理对性能敏感的项目或环境时。
在本章中,我们将开始使用 HQL 中提供的实用程序来查找导致性能低下的潜在问题。 然后,我们介绍在设计、文件格式、压缩、存储、查询和作业方面的性能注意事项的最佳做法。 在本章中,我们将介绍以下主题:
- 性能实用程序
- 设计优化
- 数据优化
- 作业优化
性能实用程序
HQL 提供EXPLAIN
和ANALYZE
语句,可用作检查和识别查询性能的实用程序。 此外,Hive 日志应包含足够详细的信息,用于性能调查和故障排除。
解释语句
配置单元提供EXPLAIN
语句以返回查询执行计划,而不运行查询。 如果我们担心查询的性能,我们可以使用它来分析查询。 EXPLAIN
语句帮助我们查看两个或多个出于相同目的的查询之间的差异。 它的语法如下:
EXPLAIN [FORMATTED|EXTENDED|DEPENDENCY|AUTHORIZATION] hql_query
可以使用以下关键字:
FORMATTED
:这提供了查询计划的格式化 JSON 版本。EXTENDED
:这为计划中的操作员提供了附加信息,如文件路径名。DEPENDENCY
:它提供 JSON 格式的输出,其中包含查询所依赖的表和分区的列表。 从配置单元 v0.10.0 开始提供AUTHORIZATION
:列出需要授权的所有实体,包括运行查询的输入和输出,以及授权失败(如果有)。 它从配置单元 v0.14.0 开始提供。
典型的查询计划包含以下三个部分。 稍后我们还将查看一个示例:
- 抽象语法树(AST):HIVE 使用名为 ANTLR 的解析器生成器(参见HQL)自动生成 http://www.antlr.org/的树语法
- 阶段依赖项:列出用于运行查询的所有依赖项和阶段数
- 阶段计划:它包含用于运行作业的重要信息,如操作员和排序顺序
以下是典型查询计划的外观。 从下面的示例中,我们可以看到AST
部分显示为 Map/Reduce 运算符树。 在STAGE DEPENDENCIES
部分中,Stage-0
和Stage-1
都是独立的根阶段。 在STAGE PLANS
部分中,Stage-1
有一个 MAP 和 REDUE,它们分别是Map Operator Tree
和Reduce Operator Tree
所指的。 在每个Map/Reduce Operator Tree
部分中,列出了与查询关键字相对应的所有运算符,以及表达式和聚合。 Stage-0
阶段没有映射和还原。 它只是一个Fetch
操作:
> EXPLAIN SELECT gender_age.gender, count(*)
> FROM employee_partitioned WHERE year=2018
> GROUP BY gender_age.gender LIMIT 2;
+----------------------------------------------------------------------+
| Explain |
+----------------------------------------------------------------------+
| STAGE DEPENDENCIES: |
| Stage-1 is a root stage |
| Stage-0 depends on stages: Stage-1 |
| |
| STAGE PLANS: |
| Stage: Stage-1 |
| Map Reduce |
| Map Operator Tree: |
| TableScan |
| alias: employee_partitioned |
| Pruned Column Paths: gender_age.gender |
| Statistics: |
| Num rows: 4 Data size: 223 Basic stats: COMPLETE Column stats: NONE |
| Select Operator |
| expressions: gender_age.gender (type: string) |
| outputColumnNames: _col0 |
| Statistics: |
| Num rows: 4 Data size: 223 Basic stats: COMPLETE Column stats: NONE |
| Group By Operator |
| aggregations: count() |
| keys: _col0 (type: string) |
| mode: hash |
| outputColumnNames: _col0, _col1 |
| Statistics: |
| Num rows: 4 Data size: 223 Basic stats: COMPLETE Column stats: NONE |
| Reduce Output Operator |
| key expressions: _col0 (type: string) |
| sort order: + |
| Map-reduce partition columns: _col0 (type: string) |
| Statistics: |
| Num rows: 4 Data size: 223 Basic stats: COMPLETE Column stats: NONE |
| TopN Hash Memory Usage: 0.1 |
| value expressions: _col1 (type: bigint) |
| Reduce Operator Tree: |
| Group By Operator |
| aggregations: count(VALUE._col0) |
| keys: KEY._col0 (type: string) |
| mode: mergepartial |
| outputColumnNames: _col0, _col1 |
| Statistics: |
| Num rows: 2 Data size: 111 Basic stats: COMPLETE Column stats: NONE |
| Limit |
| Number of rows: 2 |
| Statistics: |
| Num rows: 2 Data size: 110 Basic stats: COMPLETE Column stats: NONE |
| File Output Operator |
| compressed: false |
| Statistics: |
| Num rows: 2 Data size: 110 Basic stats: COMPLETE Column stats: NONE |
| table: |
| input format: |
| org.apache.hadoop.mapred.SequenceFileInputFormat |
| output format: |
| org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat |
| serde: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe |
| |
| Stage: Stage-0 |
| Fetch Operator |
| limit: 2 |
| Processor Tree: |
| ListSink |
+----------------------------------------------------------------------+
53 rows selected (0.232 seconds)
Ambari 配置单元视图和色调配置单元编辑器在运行查询时都有内置的可视化查询解释。 Ambari 配置单元视图显示前面的查询,如下所示:
Ambari Hive view visual explaination
分析语句
配置单元统计信息是描述更多详细信息的数据集合,例如数据库中对象的行数、文件数和原始数据大小。 统计数据是数据的元数据,收集并存储在metastore
数据库中。 HIVE 支持表、分区和列级别的统计信息。 这些统计数据用作基于配置单元成本优化器(CBO)的输入,该优化器用于根据完成查询所需的系统资源选择成本最低的查询计划。 统计信息在配置单元 v3.2.0 到 JIRA HIVE-11160(https://issues.apache.org/jira/browse/HIVE-11160)中部分自动收集,或通过表、分区和列的ANALYZE
语句手动收集,如下例所示:
- 收集现有表的统计信息。如果指定了
NOSCAN
选项,该命令将忽略文件扫描而只收集文件数量及其大小,从而运行速度更快:
> ANALYZE TABLE employee COMPUTE STATISTICS;
No rows affected (27.979 seconds)
> ANALYZE TABLE employee COMPUTE STATISTICS NOSCAN;
No rows affected (25.979 seconds)
- 收集特定或所有现有分区的统计信息:
-- Applies for specific partition
> ANALYZE TABLE employee_partitioned
> PARTITION(year=2018, month=12) COMPUTE STATISTICS;
No rows affected (45.054 seconds)
-- Applies for all partitions
> ANALYZE TABLE employee_partitioned
> PARTITION(year, month) COMPUTE STATISTICS;
No rows affected (45.054 seconds)
- 收集现有表的列的统计信息:
> ANALYZE TABLE employee_id COMPUTE STATISTICS FOR COLUMNS
employee_id;
No rows affected (41.074 seconds)
We can enable automatic gathering of statistics by specifying SET hive.stats.autogather=true
. For new tables or partitions that are populated through the INSERT OVERWRITE/INTO
statement (rather than the LOAD
statement), statistics are automatically collected in the metastore
.
一旦构建并收集了统计信息,我们就可以使用DESCRIBE EXTENDED/FORMATTED
语句检查统计信息。 从表/分区输出中,我们可以找到参数内部的统计信息,例如parameters:{numFiles=1, COLUMN_STATS_ACCURATE=true, transient_lastDdlTime=1417726247, numRows=4, totalSize=227, rawDataSize=223})
。 以下是检查表中的统计信息的示例:
-- Check statistics in a table
> DESCRIBE EXTENDED employee_partitioned PARTITION(year=2018, month=12);
-- Check statistics in a partition
> DESCRIBE EXTENDED employee;
...
parameters:{numFiles=1, COLUMN_STATS_ACCURATE=true, transient_lastDdlTime=1417726247, numRows=4, totalSize=227, rawDataSize=223}).
-- Check statistics in a column
> DESCRIBE FORMATTED employee.name;
+--------+---------+---+---+---------+--------------+
|col_name|data_type|min|max|num_nulls|distinct_count| ...
+--------+---------+---+---+---------+--------------+
| name | string | | | 0 | 5 | ...
+--------+---------+---+---+---------+--------------+
+-----------+-----------+
|avg_col_len|max_col_len| ...
+-----------+-----------+
| 5.6 | 7 | ...
+-----------+-----------+
3 rows selected (0.116 seconds)
记录 / 砍伐 / 达到 / 伐木
日志提供了解查询/作业如何运行的详细信息。 通过检查日志详细信息,我们可以识别运行时问题和可能导致性能不佳的问题。 有两种类型的日志可用,系统日志和作业日志。
系统日志包含配置单元运行状态和问题。 它在{HIVE_HOME}/conf/hive-log4j.properties
中配置。 在该文件中可以找到以下三行日志属性:
hive.root.logger=WARN,DRFA *## set logger level*
hive.log.dir=/tmp/${user.name} *## set log file path*
hive.log.file=hive.log *## set log file name*
要修改记录器级别,我们可以修改前面应用于所有用户的属性文件,也可以设置仅应用于当前用户会话的配置单元命令行配置,如$hive --hiveconf hive.root.logger=DEBUG,console
。
作业日志包含作业信息,通常由 Yarn 管理。 要检查作业日志,请使用yarn logs -applicationId <application_id>
。
设计优化
设计优化包括几种设计、数据格式和作业优化策略,以提高性能。 以下各节将详细介绍这一点。
分区表设计
配置单元分区是提高大型表查询性能的最有效方法之一。 带有分区筛选的查询将只从指定的分区(子目录)加载数据,因此它的执行速度比按非分区字段过滤的普通查询快得多。 分区键的选择始终是影响性能的重要因素。 它应该始终是一个低基数属性,以避免太多子目录开销。 以下是一些通常用作分区键的属性:
- 按日期和时间分区:当数据与日期/时间列(如
load_date
、business_date
、run_date
等)相关联时,使用日期和时间(如年、月和日(偶数小时))作为分区键 - 按位置分区:当数据与位置相关时,使用国家/地区、地区、州和城市作为分区键
- 按业务逻辑分区:当业务逻辑可以均匀地分隔数据时,使用部门、销售区域、应用、客户等作为分区键
斗台设计
与分区类似,存储桶表在 HDFS 中将数据组织到单独的文件中。 分组可以加快存储桶上的数据采样速度。 如果联接键也是存储桶列,则存储块还可以提高连接性能,因为存储块可以确保密钥出现在某个存储桶中。 选择更好的存储桶列可以使存储桶表连接执行得更好。 选择存储桶列的最佳实践是根据数据集背后的业务逻辑标识最有可能在筛选器或联接条件中使用的列。 有关更多详细信息,请参阅本章后面的作业优化部分。
指标设计
使用索引是在关系数据库中进行性能调优的一种非常常见的最佳实践。 从 HIVE v0.7.0 开始,HIVE 支持在表/分区上创建索引。 配置单元中的索引为某些操作(如WHERE
、GROUP BY
和JOIN
)提供了基于键的数据视图和更好的数据访问。 使用索引总是比全表扫描更便宜的替代方案。 在 HQL 中创建索引的命令非常简单,如下所示:
> CREATE INDEX idx_id_employee_id
> ON TABLE employee_id (employee_id)
> AS 'COMPACT'
> WITH DEFERRED REBUILD;
No rows affected (1.149 seconds)
除了这个用于存储索引列值及其块 ID 对的COMPACT
索引之外,从 v0.8.0 开始,HQL 还支持对方差较小的列值使用两个BITMAP
索引,如下例所示:
> CREATE INDEX idx_gender_employee_id
> ON TABLE employee_id (gender_age)
> AS 'BITMAP'
> WITH DEFERRED REBUILD;
No rows affected (0.251 seconds)
本例中的WITH DEFERRED REBUILD
选项阻止立即构建索引。 要构建索引,我们可以发出如下示例所示的命令:ALTER...REBUILD
。 当基表中的数据更改时,必须再次使用同一命令使索引保持最新。 这是一个原子操作。 如果在表上重建的索引以前已索引失败,则索引的状态保持不变。 请参阅此示例以构建索引:
> ALTER INDEX idx_id_employee_id ON employee_id REBUILD;
No rows affected (111.413 seconds)
> ALTER INDEX idx_gender_employee_id ON employee_id REBUILD;
No rows affected (82.23 seconds)
一旦建立了索引,就会为名称为<database_name>__<table_name>_<index_name>__
格式的每个索引创建一个新的索引表:
> SHOW TABLES '*idx*';
+-----------+---------------------------------------------+-----------+
|TABLE_SCHEM| TABLE_NAME | TABLE_TYPE|
+-----------+---------------------------------------------+-----------+
|default |default__employee_id_idx_id_employee_id__ |INDEX_TABLE|
|default |default__employee_id_idx_gender_employee_id__|INDEX_TABLE|
+-----------+---------------------------------------------+-----------+
索引表包含索引列、_bucketname
(HDFS 上的典型文件 URI)和_offsets
(每行的偏移量)。 然后,当我们查询索引表中的索引列时,可以引用该索引表,如下所示:
> DESC default__employee_id_idx_id_employee_id__;
+--------------+----------------+----------+
| col_name | data_type | comment |
+--------------+----------------+----------+
| employee_id | int | |
| _bucketname | string | |
| _offsets | array<bigint> | |
+--------------+----------------+----------+
3 rows selected (0.135 seconds)
> SELECT * FROM default__employee_id_idx_id_employee_id__;
+--------------+------------------------------------------------------+
| employee_id | _bucketname | _offsets |
+--------------+------------------------------------------------------+
| 100 | .../warehouse/employee_id/employee_id.txt | [0] |
| 101 | .../warehouse/employee_id/employee_id.txt | [66] |
| 102 | .../warehouse/employee_id/employee_id.txt | [123] |
| ... | ... ... | ... |
+--------------+-------------------------------------------+----------+
25 rows selected (0.219 seconds)
要删除索引,我们只能使用如下所示的DROP INDEX index_name ON table_name
语句。 我们不能使用DROP TABLE
语句删除索引:
> DROP INDEX idx_gender_employee_id ON employee_id;
No rows affected (0.247 seconds)
使用倾斜/临时表
除了常规的内部/外部或分区表之外,我们还应该考虑使用倾斜或临时表,以获得更好的设计和性能。
从配置单元 v0.10.0 开始,HQL 支持创建一个特殊的表来组织偏斜数据。-偏斜表可以通过自动将这些偏斜值拆分到单独的文件或目录中来提高性能。 因此,减少了文件或分区文件夹的总数。 此外,查询可以快速高效地包含或忽略此数据。 以下是用于创建倾斜表格的示例:
> CREATE TABLE sample_skewed_table (
> dept_no int,
> dept_name string
> )
> SKEWED BY (dept_no) ON (1000, 2000); -- Specify value skewed
No rows affected (3.122 seconds)
> DESC FORMATTED sample_skewed_table;
+-----------------+------------------+---------+
| col_name | data_type | comment |
+-----------------+------------------+---------+
| ... | ... | |
| Skewed Columns: | [dept_no] | NULL |
| Skewed Values: | [[1000], [2000]] | NULL |
| ... | ... | |
+-----------------+------------------+---------+
33 rows selected (0.247 seconds)
另一方面,在数据递归处理期间使用 HQL 中的临时表保留中间数据将省去重新构建公共或共享结果集的工作。 此外,临时表还可以利用存储策略设置来使用 SSD 或内存进行数据存储,这也会带来更好的性能。
数据优化
数据文件优化涵盖了数据文件在文件格式、压缩和存储方面的性能改进。
档案格式
配置单元支持TEXTFILE
、SEQUENCEFILE
、AVRO
、RCFILE
、ORC
和PARQUET
文件格式。 有两个 HQL 语句用于指定文件格式,如下所示:
CREATE TABLE ... STORE AS <file_format>
:创建表时指定文件格式ALTER TABLE ... [PARTITION partition_spec] SET FILEFORMAT <file_format>
:修改现有表格中的文件格式(仅限定义
一旦创建了以文本格式存储的表,我们就可以直接将文本数据加载到其中。 要将文本数据加载到具有其他文件格式的表格中,我们可以首先将数据加载到以文本形式存储的表格中,在该表格中,我们使用INSERT OVERWRITE/INTO TABLE ... SELECT
按钮从表格中选择数据,然后将数据插入到具有其他文件格式的表格中。
To change the default file format for table creation, we can set the hive.default.fileformat = <file_format>
property for all tables or hive.default.fileformat.managed = <file_format>
only for internal/managed tables.
作为面向行的文件存储格式的TEXT
、SEQUENCE
和AVRO
文件不是最佳解决方案,因为即使只请求一列,查询也必须读取整行。 另一方面,针对这一问题,采用行列混合存储文件格式,如RCFILE
、ORC
、PARQUET
等,HQL 支持的文件格式详情如下:
TEXTFILE
:这是创建表的默认文件格式。 对于此格式,数据以明文形式存储。 文本文件自然是可拆分的,并且能够并行处理。 还可以使用 GZip、LZO 和 Snappy 等算法对其进行压缩。 但是,大多数压缩文件不能拆分以进行并行处理。 因此,他们只使用一个具有单个映射器的作业来缓慢地处理数据。 使用压缩文本文件的最佳实践是确保文件不太大,并且接近几个 HDFS 块大小。SEQUENCEFILE
:这是键/值对的二进制存储格式。 序列文件的好处是它比文本文件更紧凑,并且非常适合 MapReduce 输出格式。 序列文件可以压缩到记录级或块级,其中块级具有较好的压缩比。 要启用块级压缩,我们需要使用以下设置:set hive.exec.compress.output=true;
和set io.seqfile.compression.type=BLOCK;
。AVRO
:这也是二进制格式。 不仅如此,它还是一个序列化和反序列化框架。 Avro提供了一个数据模式,该模式描述数据结构并处理模式更改,如添加、重命名和删除列。 该模式与数据一起存储,以供进一步处理。 考虑到AVRO
在处理模式演变方面的优势,建议在映射源数据时使用它,源数据可能会随时间发生模式更改。RCFILE
:这是记录列文件的缩写。 它是一个平面文件,由二进制密钥/值对组成,与序列文件有许多相似之处。RCFile
将数据水平拆分成行组。 一个或多个组存储在 HDFS 文件中。 然后,RCFile
以列格式保存行组数据,方法是先保存所有行的第一列,然后保存所有行的第二列,依此类推。 这种格式是可拆分的,允许配置单元跳过数据中不相关的部分,从而更快、更便宜地获得结果。ORC
:这是优化行列的缩写。 它从配置单元 v0.11.0 开始提供。 可以将ORC
格式视为RCFILE
的改进版本。 默认情况下,它提供 256 MB 的较大块大小(RCFILE
为 4 MB,SEQUENCEFILE
为 1 MB),针对 HDFS 上的大型顺序读取进行了优化,以获得更高的吞吐量和更少的文件,以减少 NameNode 中的过载。 与依赖metastore
了解数据类型的RCFILE
不同,ORC
文件通过使用特定的编码器来了解数据类型,因此它可以根据不同的类型优化压缩。 它还存储有关列的基本统计信息,如MIN
、MAX
、SUM
和COUNT
,以及可用于跳过无关紧要的行块的轻量级索引。PARQUET
:这是另一种设计与ORC
类似的行列式文件格式。 更重要的是,与主要由 Hive、PIG 和星火支持的ORC
相比,Parquet 对生态系统中的大多数项目都有更广泛的支持。PARQUET
利用 Google 的 DREMEL 设计中的最佳实践(参见http://research.google.com/pubs/pub36632.html)来支持数据的嵌套结构。PARQUET
从配置单元 v0.10.0 开始受插件支持,在 v0.13.0 之后获得原生支持。
根据使用的技术堆栈,如果配置单元是定义或处理数据的主要工具,则建议使用ORC
格式。 如果您在生态系统中使用多种工具,PARQUET
在适应性方面是更好的选择。
Hadoop Archive File (HAR) is another type of file format to pack HDFS files into archives. This is an option (not a good option) for storing a large number of small-sized files in HDFS, as storing a large number of small-sized files directly in HDFS is not very efficient. However, HAR
has other limitations, such as an immutable archive process, not being splittable, and compatibility issues. For more information about HAR
and archiving, please refer to the Hive Wiki at https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Archiving.
压缩 / 压紧 / 压挤 / 压榨,压迫
通过适当压缩中间和最终输出数据,配置单元中的压缩技术可以显著减少映射器和减少器之间传输的数据量。 因此,查询将具有更好的性能。 要压缩在多个 MapReduce 作业之间生成的中间文件,我们需要在命令行会话或hive-site.xml
文件中设置以下属性(默认情况下为false
):
> SET hive.exec.compress.intermediate=true
然后,我们需要决定配置哪个压缩编解码器。 下表列出了通常支持的各种编解码器:
| 压缩 | 编解码器 | 扩展 | 可拆分 |
| 显示 | org.apache.hadoop.io.compress.DefaultCodec
| .deflate
| (化学元素)氮 |
| Gzip | org.apache.hadoop.io.compress.GzipCodec
| .gz
| (化学元素)氮 |
| Bzip2 | org.apache.hadoop.io.compress.BZip2Codec
| .gz
| 英语字母表中第二十五个字母 / Y 字形 / Y 项 |
| LZO | com.apache.compression.lzo.LzopCodec
| .lzo
| (化学元素)氮 |
| LZ4 | org.apache.hadoop.io.compress.Lz4Codec
| .lz4
| (化学元素)氮 |
| 厉声说话的 / 恶声恶气的 / 短小精悍的 / 时髦的 | org.apache.hadoop.io.compress.SnappyCodec
| .snappy
| (化学元素)氮 |
DEVATE(.deflate
)是一种默认编解码器,具有均衡的压缩比和 CPU 成本。 Gzip 的压缩比非常高,其 CPU 成本也很高。 Bzip2 是可拆分的,但考虑到其巨大的 CPU 成本,就像 Gzip 一样,它对于压缩来说太慢了。 LZO 文件本身不能拆分,但我们可以对其进行预处理(使用com.hadoop.compression.lzo.LzoIndexer
)以创建确定文件拆分的索引。 当谈到 CPU 成本和压缩比的平衡时,LZ4 或 Snappy 比 Deflate 做得更好,但 Snappy 更受欢迎。 由于大多数压缩文件是不可拆分的,因此不建议压缩单个大文件。 最佳实践是生成几个 HDFS 块大小的压缩文件,以便每个文件花费更少的处理时间。 压缩编解码器可以在mapred-site.xml
、hive-site.xml
或命令行会话中指定,如下所示:
> SET hive.intermediate.compression.codec=
org.apache.hadoop.io.compress.SnappyCodec
中间压缩只会为需要多个 MapReduce 作业的特定作业节省磁盘空间。 为了进一步节省磁盘空间,可以压缩实际的配置单元输出文件。 当hive.exec.compress.output
属性设置为true
时,配置单元将使用由mapreduce.output.fileoutputformat.compress.codec
属性配置的编解码器来压缩 HDFS 中的数据,如下所示。 这些属性可以在hive-site.xml
或命令行会话中设置:
> SET hive.exec.compress.output=true
> SET mapreduce.output.fileoutputformat.compress.codec=
org.apache.hadoop.io.compress.SnappyCodec
存储优化
频繁使用或扫描的数据可以识别为热门数据。 通常,热数据上的查询性能对整体性能至关重要。 为热数据增加 HDFS 中的数据复制系数(请参见以下示例)可能会增加作业在本地命中数据的可能性,并提高整体性能。 但是,这是对存储的权衡:
$ hdfs dfs -setrep -R -w 4 /user/hive/warehouse/employee
Replication 4 set: /user/hive/warehouse/employee/000000_0
另一方面,过多的文件或冗余可能会使 NameNode 的内存耗尽,特别是大量小于 HDFS 块大小的小文件。 Hadoop 本身已经有一些解决方案,可以通过以下方式处理许多小文件问题:
- Hadoop Archive/
HAR
:这些是前面介绍的打包小文件的工具包。 SEQUENCEFILE
格式:这是一种可用于将小文件压缩成大文件的格式。CombineFileInputFormat
:一种InputFormat
类型,用于在映射和缩减处理之前合并小文件。 它是配置单元的默认InputFormat
(参见https://issues.apache.org/jira/browse/HIVE-2245)。- HDFS 联合:支持多个名称节点管理更多文件。
如果我们安装了 Hadoop 生态系统中的其他工具,我们还可以利用它们,例如:
- HBase 具有更小的数据块大小和更好的文件格式,可处理较小的文件存储和访问问题
- Flume NG 可用作将小文件合并为大文件的管道
- 开发并计划了一个文件合并程序,用于在 HDFS 中或在将文件加载到 HDFS 之前合并小文件
对于配置单元,我们可以使用以下配置来合并查询结果文件,避免重新创建小文件:
hive.merge.mapfiles
:这会在仅地图作业结束时合并小文件。 默认情况下,它是true
。hive.merge.mapredfiles
:这会在 MapReduce 作业结束时合并小文件。 将其设置为TRUE,,因为默认值为false
。hive.merge.size.per.task
:此选项定义作业结束时合并文件的大小。 默认值为 256,000,000。hive.merge.smallfiles.avgsize
:这是触发文件合并的阈值。 默认值为 16,000,000。
当作业的平均输出文件大小小于hive.merge.smallfiles.avgsize
和属性指定的值,并且hive.merge.mapfiles
(对于纯地图作业)和hive.merge.mapredfiles
(对于 MapReduce 作业)都设置为TRUE时,配置单元将启动附加的 MapReduce 作业以将输出文件合并到大文件中。
作业优化
作业优化包括在作业运行模式、JVM 重用、作业并行运行和查询连接优化等方面提高性能的经验和技能。
本地模式
Hadoop 可以在独立、伪分布式和完全分布式模式下运行。 大多数情况下,我们需要将其配置为在完全分布式模式下运行。 当要处理的数据较小时,由于完全分布式模式的启动时间比作业处理时间更长,因此启动分布式数据处理是一种开销。 从 v0.7.0 开始,配置单元支持使用以下设置自动转换作业以在本地模式下运行:
> SET hive.exec.mode.local.auto=true; -- default false > SET hive.exec.mode.local.auto.inputbytes.max=50000000;
> SET hive.exec.mode.local.auto.input.files.max=5; -- default 4
作业必须满足以下条件才能在本地模式下运行:
- 作业的总输入大小小于
hive.exec.mode.local.auto.inputbytes.max
设置的值 - 地图任务总数小于设置的值
hive.exec.mode.local.auto.input.files.max
- 所需的 Reduce 任务总数为 1 或 0
JVM 重用
默认情况下,Hadoop 为每个 map 或 Reduce 作业启动一个新的 JVM,并并行运行 map 或 Reduce 任务。 当 map 或 Reduce 作业是只运行几秒钟的轻量级作业时,JVM 启动过程可能会带来很大的开销。 Hadoop 可以通过共享 JVM 来选择重用 JVM,以串行而不是并行地运行映射器/减少器。 JVM 重用适用于映射或减少同一作业中的任务。 来自不同作业的任务将始终在单独的 JVM 中运行。 要启用重用,我们可以使用以下属性为 JVM 重用设置单个作业的最大任务数。 默认值为 1。如果设置为-1,则没有限制:
> SET mapreduce.job.jvm.numtasks=5;
并行执行
Hive 查询通常被转换成由默认序列执行的多个阶段。 这些阶段并不总是相互依赖的。 相反,它们可以并行运行,以减少总体作业运行时间。 我们可以使用以下设置启用此功能,并设置并行运行的预期作业数:
> SET hive.exec.parallel=true; -- default false > SET hive.exec.parallel.thread.number=16; -- default 8
并行执行将提高集群利用率。 如果集群的利用率已经很高,并行执行在总体性能方面不会有太大帮助。
连接优化
我们已经在章,的数据关联和范围中讨论了不同类型的配置单元联接的优化。 在这里,我们将简要回顾联接改进的关键设置。
公共连接
公共联接也称为 Reduce 侧联接。 它是 HQL 中的一个基本连接,并且在大多数情况下都有效。 对于普通连接,我们需要确保大表位于最右侧或由 HIT 指定,如下所示:
/*+ STREAMTABLE(stream_table_name) */
地图连接
当其中一个连接表足够小,可以放入内存时,就会使用映射连接,因此它速度很快,但受到表大小的限制。 从配置单元 v0.7.0 开始,它可以使用以下设置自动转换地图连接:
> SET hive.auto.convert.join=true; -- default true after v0.11.0 > SET hive.mapjoin.smalltable.filesize=600000000; -- default 25m
> SET hive.auto.convert.join.noconditionaltask=true; -- default value above is true so map join hint is not needed
> SET hive.auto.convert.join.noconditionaltask.size=10000000; -- default value above controls the size of table to fit in memory
一旦启用联接和自动转换,配置单元将自动检查较小的表文件大小是否大于hive.mapjoin.smalltable.filesize
指定的值,然后将联接转换为普通联接。 如果文件大小小于此阈值,它将尝试将公共连接转换为映射连接。 一旦启用了自动转换联接,就不需要在查询中提供映射联接提示。
桶图连接
存储桶映射连接是应用于存储桶表的一种特殊类型的映射连接。 要启用存储桶图加入,我们需要启用以下设置:
> SET hive.auto.convert.join=true;
> SET hive.optimize.bucketmapjoin=true; -- default false
在存储桶映射连接中,所有连接表必须是存储桶表并连接到存储桶列。 此外,较大表格中的存储桶编号必须是较小表格中的存储桶编号的倍数。
排序合并存储桶(SMB)联接
SMB 是对具有相同的排序、存储桶和联接条件列的存储桶表执行的联接。 它从两个桶表中读取数据,并在桶表上执行公共连接(映射和 Reduce 触发)。 我们需要启用以下属性才能使用 SMB:
> SET hive.input.format=
> org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;
> SET hive.auto.convert.sortmerge.join=true;
> SET hive.optimize.bucketmapjoin=true;
> SET hive.optimize.bucketmapjoin.sortedmerge=true;
> SET hive.auto.convert.sortmerge.join.noconditionaltask=true;
排序合并桶图(SMBM)联接
SMBM 联接是一种特殊的桶联接,但仅触发映射端联接。 它可以避免像映射连接那样缓存内存中的所有行。 要执行 SMBM 联接,联接表必须具有相同的存储桶列、排序列和联接条件列。 要启用此类联接,我们需要启用以下设置:
> SET hive.auto.convert.join=true;
> SET hive.auto.convert.sortmerge.join=true
> SET hive.optimize.bucketmapjoin=true;
> SET hive.optimize.bucketmapjoin.sortedmerge=true;
> SET hive.auto.convert.sortmerge.join.noconditionaltask=true;
> SET hive.auto.convert.sortmerge.join.bigtable.selection.policy=
org.apache.hadoop.hive.ql.optimizer.TableSizeBasedBigTableSelectorForAutoSMJ;
斜连接
当处理分布高度不均匀的数据时,可能会发生数据歪斜,导致少数计算节点必须处理大量计算。 如果发生数据倾斜,以下设置将通知配置单元正确优化:
> SET hive.optimize.skewjoin=true; --If there is data skew in join, set it to true. Default is false.
> SET hive.skewjoin.key=100000;
--This is the default value. If the number of key is bigger than
--this, the new keys will send to the other unused reducers.
Skewed data could occur with the GROUP BY
data too. To optimize it, we need set hive.groupby.skewindata=true
to use the preceding settings to enable skew data optimization in the GROUP BY
result. Once configured, Hive will first trigger an additional MapReduce job whose map output will randomly distribute to the reducer to avoid data skew.
有关联接优化的更多信息,请参考位于https://cwiki.apache.org/confluence/display/Hive/LanguageManual+JoinOptimization和https://cwiki.apache.org/confluence/display/Hive/Skewed+Join+Optimization的配置单元维基。
作业引擎
配置单元支持在不同引擎上运行作业。 发动机的选择也会影响整体性能。 然而,与其他设置相比,这是一个更大的变化。 此外,此更改需要重新启动服务,而不是临时使其在命令行会话中生效。 以下是设置引擎的语法以及每个引擎的详细信息:
SET hive.execution.engine=<engine>; -- <engine> = mr|tez|spark
mr
:这是默认引擎 MapReduce。 在配置单元 v2.0.0 之后,它已弃用。tez
:TEZ(http://tez.apache.org/)是一个构建在 Yarn 上的应用框架,它可以为一般的数据处理任务执行复杂的有向无环图和(DAGs)。 TEZ 进一步将 MAP 和 Reduce 作业拆分成更小的任务,并以灵活高效的方式将它们组合在一起执行。 TEZ 被认为是 MapReduce 框架灵活而强大的继任者。 TEZ 已经为生产做好了准备,并且大部分时间都被用来取代 MR 引擎。spark
:Spark 是另一个通用大数据框架。 它的组件 Spark SQL 支持 HQL 的一个子集,并提供类似于 HQL 的语法。 通过使用 Spark 上的配置单元,Spark 可以利用 Spark 的内存计算模型以及 Spark 成熟的基于成本的优化器。 然而,星火之上的 Hive 需要手动配置,在实际生产中仍然缺乏坚实的使用案例。 有关“星火上的 Hive”的更多细节,请参考(https://cwiki.apache.org/confluence/display/Hive/Hive+on+Spark%3A+Getting+Started)的维基页面。mr3
:mr3 是另一个实验引擎(https://mr3.postech.ac.kr/)。 它类似于 TEZ,但增强了设计、更好的性能和更多的功能。 MR3 记录为可投入生产使用,并支持 TEZ 的所有主要功能,例如基于 Kerberos 的安全性、身份验证和授权、容错和恢复。 但是,它缺乏可靠的生产使用案例和生产部署的最佳实践,也缺乏对 CDH 或 HDP 分发的支持。
Live Long And Process (LLAP) functionality was added in Hive v2.0.0. It combines a live long running query service and intelligent in-memory caching to deliver fast queries. Together with a job engine, LLAP provides a hybrid execution model to improve overall Hive performance. LLAP needs to work through Apache Slider (https://slider.incubator.apache.org/) and only works with Tez for now. In the future, it will support other engines. The recent HDP has provided LLAP supported thought Tez.
优化器
与关系数据库类似,配置单元在提交最终执行之前生成并优化每个查询的逻辑和物理执行计划。 目前在配置单元中有两个主要的优化器来进一步优化总体上的查询性能,向量化和基于成本的优化(CBO)。
矢量化优化
矢量化优化可以同时处理更大批量的数据,而不是一次处理一行,从而显著降低计算开销。 每个批处理由一个列向量组成,该列向量通常是一个基元类型数组。 操作是在整个列向量上执行的,这改进了指令流水线和高速缓存的使用。 文件必须以ORC
格式存储,才能使用矢量化。 有关矢量化的更多详细信息,请参考 Hive 维基(https://cwiki.apache.org/confluence/display/Hive/Vectorized+Query+Execution)。 要启用矢量化,我们需要使用以下设置:
> SET hive.vectorized.execution.enabled=true; -- default false
基于成本的优化
配置单元中的 CBO 由 Apache 方解石(http://calcite.apache.org/)提供支持,这是一个开源的、基于成本的企业级逻辑优化器和查询执行框架。 HIVE CBO 通过检查由ANALYZE
语句或metastore
本身收集的查询成本来生成高效的执行计划,最终缩短查询执行时间并降低资源使用率。 要使用 CBO,请设置以下属性:
> SET hive.cbo.enable=true; -- default true after v0.14.0
> SET hive.compute.query.using.stats=true; -- default false
> SET hive.stats.fetch.column.stats=true; -- default false
> SET hive.stats.fetch.partition.stats=true; -- default true
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,我们首先介绍了如何使用EXPLAIN
和ANALYZE
语句识别性能瓶颈。 然后,我们谈到了使用表、分区和索引时的性能优化设计。 我们还介绍了数据文件优化,包括文件格式、压缩和存储。 在本章的最后,我们讨论了作业优化、作业引擎和优化器。 读完本章后,您应该能够在配置单元中进行性能故障排除和调优。 在下一章中,我们将讨论配置单元的功能扩展。
八、可扩展性注意事项
尽管 Hive 提供了许多内置功能,但在特殊情况下,用户可能需要超出所提供的功能。 在这种情况下,我们可以在三个主要方面扩展 Hive 的功能:
- 用户定义函数(UDF):这提供了一种使用外部函数(主要是用 Java 编写的)扩展功能的方法,该函数可以在 HQL 中求值
- HPL/SQL:这为 HQL 提供过程语言编程支持
- 流:这将用户自己的定制程序插入到数据流中
- serDe:这代表序列化和反序列化,并提供了一种使用定制文件格式序列化或反序列化数据的方法
在本章中,我们将更详细地讨论它们中的每一个。
用户定义函数
用户定义函数提供了一种在 HQL 查询期间使用用户自己的应用/业务逻辑处理列值的方法。 例如,用户定义的函数可以使用外部机器学习库执行特征清理、验证来自其他服务的用户访问、将多个值合并为一个或多个、执行特殊的数据编码或加密以及常规 HQL 运算符和函数范围之外的其他操作。 配置单元定义了以下三种类型的用户定义函数,它们是可扩展的:
-
UDF
:代表用户自定义函数,按行操作,一行输出一个结果,比如大多数内置的数学函数和字符串函数。 -
UDAF
:代表用户定义的聚合函数,按行或按组操作,结果输出整个表一行或每组一行,如:max(...)
和count(...)
内置函数。 -
UDTF
:它代表用户定义的表格生成函数,该函数也是按行操作的,但是会产生多个行/表,比如explode(...)
函数。UDTF
可以在SELECT
或LATERAL VIEW
语句之后使用。
Although all In functions in HQL are implemented in Java, UDF can also be implemented in any JVM-compatible language, such as Scala. In this book, we only focus on writing user-defined functions in Java.
在接下来的几节中,我们将开始更详细地查看每种用户定义函数的 Java 代码模板。
自定义项代码模板
规则UDF
的代码模板如下:
package com.packtpub.hive.essentials.hiveudf;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.udf.UDFType;
import org.apache.hadoop.io.Text;
*// Other libraries my needed*
*// These information is show by "desc function <function_name>"*
@Description(
name = "udf_name",
value = "_FUNC_(arg1, ... argN) - description for the function.",
extended = "decription with more details, such as syntax, examples."
)
@UDFType(deterministic = true, stateful = false)
public class udf_name extends UDF {
// *evaluate() is the only necessary function to overwrite*
public Text evaluate(){
/*
* *H****ere** to impelement core function logic*
*/
return "return the udf result";
}
// *override is supported*
public String evaluate(<Type_arg1> arg1,..., <Type_argN> argN){
/*
* *Do something here*
*/
return "return the udf result";
}
}
在前面的模板中,包定义和导入应该是不言而喻的。 除了前三个必需库之外,我们还可以导入任何需要的库。 @Description
注释是特定于配置单元的有用注释,可提供函数和用法。 在value
属性中定义的信息将显示在DESC FUNCTION
语句中。 在extended
属性中定义的信息将显示在DESCRIBE FUNCTION EXTENDED
语句中。 @UDFType
注释指定函数的预期行为。 确定性UDF
(deterministic = true
)是在传递相同参数时始终给出相同结果的函数,例如length(...)
和max(...)
。 另一方面,非确定性(deterministic = false
)UDF
可以为同一组参数返回不同的结果,例如,unix_timestamp()
,它返回默认时区的当前时间戳。 Stateful(stateful = true
)属性允许函数跨行保留一些静态变量,例如为表行分配序列号的row_number()
。
所有UDF
都应该从org.apache.hadoop.hive.ql.exec.UDF
类扩展,因此UDF
子类必须实现evaluate()
方法,该方法也可以重写用于不同的目的。 在此方法中,我们可以使用 Java、Hadoop 和配置单元库和数据类型实现预期的功能逻辑和异常处理。
UDAF 代码模板
在本节中,我们将介绍UDAF
代码模板,它是从org.apache.hadoop.hive.ql.exec.UDAF
和类扩展而来的。 代码模板如下:
package com.packtpub.hive.essentials.hiveudaf;
import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.udf.UDFType;
@Description(
name = "udaf_name",
value = "_FUNC_(arg1, arg2, ... argN) - description for the function",
extended = "description with more details, such as syntax, examples."
)
@UDFType(deterministic = false, stateful = true)
public final class udaf_name extends UDAF {
/**
* *The internal state of an aggregation function.* *
* *Note that this is only needed if the internal state* * *cannot be represented by a primitive type.* *
* *The internal state can contain fields with types like* * *ArrayList<String> and HashMap<String,Double> if needed.*
*/
public static class UDAFState {
private <Type_state1> state1;
private <Type_stateN> stateN;
}
/**
* *The actual class for doing the aggregation. Hive will* * *automatically look for all internal classes of the UDAF* * *that implements UDAFEvaluator.*
*/
public static class UDAFExampleAvgEvaluator implements UDAFEvaluator {
UDAFState state;
public UDAFExampleAvgEvaluator() {
super();
state = new UDAFState();
init();
}
/**
* *Reset the state of the aggregation.*
*/
public void init() {
/*
* *Examples for initializing state.*
*/
state.state1 = 0;
state.stateN = 0;
}
/**
* *Iterate through one row of original data.* *
* *The number and type of arguments need to be the same as we* * *call this UDAF from the Hive command line.* ** *This function should always return true.*
*/
public boolean iterate(<Type_arg1> arg1,..., <Type_argN> argN){
/*
* *Add logic here for how to do aggregation if there is* * *a new value to be aggregated.*
*/
return true;
}
/**
* *Called on the mapper side on different data nodes.* * *Terminate a partial aggregation and return the state.* * *If the state is a primitive, just return primitive Java* * *classes like Integer or String.*
*/
public UDAFState terminatePartial() {
/*
* *Check and return a partial result in expectations.*
*/
return state;
}
/**
* *Merge with a partial aggregation.* *
* *This function should always have a single argument,* * *which has the same type as the return value of* * *terminatePartial().*
*/
public boolean merge(UDAFState o) {
/*
* *Define operations how to merge the result calculated* * *from all data nodes.*
*/
return true;
}
/**
* *Terminates the aggregation and returns the final result.*
*/
public long terminate() {
/*
* *Check and return final result in expectations.*
*/
return state.stateN;
}
}
}
UDAF 必须是包含一个或多个实现org.apache.hadoop.hive.ql.exec.UDAFEvaluator
的嵌套静态类的org.apache.hadoop.hive.ql.exec.UDAF
的子类。 确保实现UDAFEvaluator
的内部类定义为 public。 否则,配置单元将无法使用反射并确定UDAFEvaluator
实现。 我们还应该实现前面已经描述过的五个必需函数init()
、iterate()
、terminatePartial()
、merge()
和terminate()
。
还可以通过扩展GenericUDF
和GenericUDAFEvaluator
类来实现UDF
和UDAF
,以避免使用 Java 反射以获得更好的性能。 此外,泛型函数支持复杂数据类型(如MAP
、ARRAY
和STRUCT
)作为参数,而UDF
和UDAF
函数不支持。 有关GenericUDAF
的更多信息,请参考https://cwiki.apache.org/confluence/display/Hive/GenericUDAFCaseStudy上的 Hive 维基。
UDTF 代码模板
要实现UDTF
,只有一个方法从org.apache.hadoop.hive.ql.exec.GenericUDTF
扩展而来。 没有普通的UDTF
类。 我们需要实现三个方法:initialize()
、process()
和close()
。 UDTF
将调用initialize()
方法,该方法返回函数输出的信息,如数据类型和输出数量。 然后,调用process()
方法来执行带参数的核心函数逻辑并转发结果。 最后,如果需要,close()
方法将进行适当的清理。 UDTF
的代码模板如下:
package com.packtpub.hive.essentials.hiveudtf;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
@Description(
name = "udtf_name",
value = "_FUNC_(arg1, arg2, ... argN) - description for the function",
extended = "description with more detail, such as syntax, examples."
)
public class udtf_name extends GenericUDTF {
private PrimitiveObjectInspector stringOI = null;
/**
* *This method will be called exactly once per instance.* * *It performs any custom initialization logic we need.* * *It is also responsible for verifying the input types and* * *specifying the output types.*
*/
@Override
public StructObjectInspector initialize(ObjectInspector[] args)
throws UDFArgumentException {
// *Check number of arguments.*
if (args.length != 1) {
throw new UDFArgumentException(
"The UDTF should take exactly one argument");
}
/*
* *Check that the input ObjectInspector[] array contains a* * *single PrimitiveObjectInspector of the Primitive type,* * *such as String.*
*/
if (args[0].getCategory() != ObjectInspector.Category.PRIMITIVE
&&
((PrimitiveObjectInspector) args[0]).getPrimitiveCategory()
!=
PrimitiveObjectInspector.PrimitiveCategory.STRING) {
throw new UDFArgumentException(
"The UDTF should take a string as a parameter");
}
stringOI = (PrimitiveObjectInspector) args[0];
/*
* *Define the expected output for this function, including*
* *each alias and types for the aliases.*
*/
List<String> fieldNames = new ArrayList<String>(2);
List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(2);
fieldNames.add("alias1");
fieldNames.add("alias2");
fieldOIs.add(PrimitiveObjectInspectorFactory.
javaStringObjectInspector);
fieldOIs.add(PrimitiveObjectInspectorFactory.
javaIntObjectInspector);
//*Set up the output schema.*
return ObjectInspectorFactory.
getStandardStructObjectInspector(fieldNames, fieldOIs);
}
/**
* *This method is called once per input row and generates* * *output. The "forward" method is used (instead of* * *"return") in order to specify the output from the function.*
*/
@Override
public void process(Object[] record) throws HiveException {
/*
* *We may need to convert the object to a primitive type*
* *before implementing customized logic.*
*/
final String recStr = (String) stringOI.
getPrimitiveJavaObject(record[0]);
//*Emit newly created structs after applying customized logic.*
forward(new Object[] {recStr, Integer.valueOf(1)});
}
/**
* *This method is for any cleanup that is necessary before* * *returning from the UDTF. Since the output stream has* * *already been closed at this point, this method cannot* * *emit more rows.*
*/
@Override
public void close() throws HiveException {
//*Do nothing*.
}
}
开发和部署
我们将通过一个示例完成整个开发和部署步骤。 让我们创建一个名为toUpper
的简单函数,通过以下开发和部署步骤将字符串转换为大写:
- 下载并安装 Java IDE,如 Eclipse 或 IntelliJ IDEA。
- 启动 IDE 并创建一个 Java 项目
- 右键单击项目以选择构建路径|配置构建路径|添加外部 Jars 选项。 它将打开一个新窗口。 导航到包含配置单元和 Hadoop 库的目录。 然后,选择并添加我们需要导入的所有 JAR 文件。 我们还可以使用 maven(http://maven.apache.org/)自动解析库依赖关系;本书的示例代码中给出了适当的
pom.xml
文件,以便作为 Maven 项目导入。 - 在 IDE 中,根据前面提到的 UDF 模板创建以下
ToUpper.java
文件:
package hive.essentials.hiveudf;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;
class ToUpper extends UDF {
public Text evaluate(Text input) {
if(input == null) return null;
return new Text(input.toString().toUpperCase());
}
}
- 使用
hiveudf-1.0.jar
编译并构建项目 JAR 文件。 - 使用
hdfs dfs -put hiveudf-1.0.jar /app/hive/function/
命令将 JAR 文件上传到 HDFS。 - 将该函数创建为仅在当前会话中有效的临时函数。 从配置单元 v0.13.0 开始,我们还可以创建永久函数,该函数永久注册到元存储区,并且可以在所有查询和会话中引用:
> CREATE TEMPORARY FUNCTION tmptoUpper
> as 'com.packtpub.hive.essentials.hiveudf.toupper';
> USING JAR 'hdfs:///app/hive/function/hiveudf-1.0.jar';
> CREATE FUNCTION toUpper -- Create permanent function
> as 'hive.essentials.hiveudf.ToUpper'
> USING JAR 'hdfs:///app/hive/function/hiveudf-1.0.jar';
- 验证并检查功能:
> SHOW FUNCTIONS ToUpper;
> DESCRIBE FUNCTION ToUpper;
> DESCRIBE FUNCTION EXTENDED ToUpper;
+----------------------------------------------------+
| tab_name |
+----------------------------------------------------+
| toUpper(value) - Returns upper case of value. |
| Synonyms: default.toupper |
| Example: |
| > SELECT toUpper('will'); |
| WILL |
| Function class:hive.essentials.hiveudf.ToUpper |
| Function type:PERSISTENT |
| Resource:hdfs:///app/hive/function/hiveudf-1.0.jar |
+----------------------------------------------------+
- 重新加载并使用 HQL 中的函数:
> RELOAD FUNCTION; -- Reload all invisible functions if needed
> SELECT
> name, toUpper(name) as cap_name, tmptoUpper(name) as cname
> FROM employee;
+---------+----------+----------+
| name | cap_name | c_name |
+---------+----------+----------+
| Michael | MICHAEL | MICHAEL |
| Will | WILL | WILL |
| Shelley | SHELLEY | SHELLEY |
| Lucy | LUCY | LUCY |
+---------+----------+----------+
4 rows selected (0.363 seconds)
- 在需要时删除该函数:
> DROP TEMPORARY FUNCTION IF EXISTS tmptoUpper;
> DROP FUNCTION IF EXISTS toUpper;
HPL/SQL
从 HifeV2.0.0 开始,Hadoop 过程语言 SQL(HPL/SQL)(http://www.hplsql.org/))可用于在 Hieve 中提供存储过程编程。 HPL/SQL 支持配置单元、Spark SQL 和 Impala,并与 Oracle、DB2、MySQL 和 TSQL 标准兼容。 它的好处之一是使现有的数据库存储过程迁移到配置单元变得简单而高效。 使用 HPL/SQL 不需要 Java 技能就可以实现前面提到的通过 UDF 实现的功能。 与 UDF 相比,HPL/SQL 的性能稍慢一些,在生产应用中仍然是新事物。
以下是创建存储存储过程的示例。 HPL/SQL 支持创建Function
和Procedure
:
$ cat getEmpCnt.pl
CREATE PROCEDURE getCount()
BEGIN
DECLARE cnt INT = 0;
SELECT COUNT(*) INTO cnt FROM employee;
PRINT 'Users cnt: ' || cnt;
END;
call getCount(); -- Call a procedure
为了运行过程,我们需要通过提供hiveserver2
连接 URL 在hplsql-site.xml
中设置数据库连接,如下所示。 之后,HPL/SQL 可以使用默认连接提交过程语句或文件:
SQLhplsql
命令与带-f
选项的hive
命令位于同一文件夹中,如下所示:
$ cat /opt/hive2/conf/hplsql-site.xml
<configuration>
<property>
<name>hplsql.conn.default</name>
<value>hive2conn</value>
</property>
<property>
<name>hplsql.conn.hive2conn</name>
<value>org.apache.hive.jdbc.HiveDriver;jdbc:hive2://localhost:10500</value>
</property>
</configuration>
然后,我们可以呼叫 HPL:
$cd /opt/hive2/bin
$ ./hplsql -f getEmpCnt.pl
SLF4J: Class path contains multiple SLF4J bindings.
...
Open connection: jdbc:hive2://localhost:10500 (1.02 sec)
Starting query
Query executed successfully (569 ms)
Users cnt: 4
流
HIVE 还可以利用 Hadoop 中的流功能以另一种方式转换数据。 流 API 打开通往外部进程(如脚本)的 I/O 管道。 然后,该过程从标准输入读取数据,并通过标准输出将结果写出。 在 HQL 中,我们可以直接使用TRANSFORM
子句来嵌入用命令、shell 脚本、Java 或其他编程语言编写的映射器和削减器脚本。 尽管流在进程之间使用序列化/反序列化带来了开销,但它为非 Java 开发人员提供了一种简单的编码模式。 TRANSFORM
子句的语法如下:
FROM (
FROM src
SELECT TRANSFORM '(' expression (',' expression)* ')'
(inRowFormat)?
USING 'map_user_script'
(AS colName (',' colName)*)?
(outRowFormat)? (outRecordReader)?
(CLUSTER BY?|DISTRIBUTE BY? SORT BY?) src_alias
)
SELECT TRANSFORM '(' expression (',' expression)* ')'
(inRowFormat)?
USING 'reduce_user_script'
(AS colName (',' colName)*)?
(outRowFormat)? (outRecordReader)?
默认情况下,用户脚本的INPUT
值如下:
- 转换为
STRING
值的列 - 由制表符分隔
NULL
转换为文本字符串的N
值(区分NULL
值和空字符串)
默认情况下,用户脚本的OUTPUT
值如下:
- 视为制表符分隔的
STRING
列 - N将重新解释为
NULL
- 生成的
STRING
列将转换为 TABLE 声明中指定的数据类型
可以用ROW FORMAT
覆盖这些默认值。 使用 Python 脚本upper.py
进行流式传输的示例如下:
$cat upper.py
#!/usr/bin/env python
'''
This is a script to upper all cases
'''
import sys
def main():
try:
for line in sys.stdin:
n = line.strip()
print n.upper()
except:
return None
if __name__ == "__main__":main()
通过以正常方式运行脚本来测试该脚本,如下所示:
$ echo "Will" | python upper.py
$ WILL
使用 HQL 调用脚本:
> ADD FILE /tmp/upper.py;
> SELECT
> TRANSFORM (name,work_place[0])
> USING 'python upper.py' as (CAP_NAME,CAP_PLACE)
> FROM employee;
+-----------+------------+
| cap_name | cap_place |
+-----------+------------+
| MICHAEL | MONTREAL |
| WILL | MONTREAL |
| SHELLEY | NEW YORK |
| LUCY | VANCOUVER |
| STEVEN | NULL |
+-----------+------------+
5 rows selected (30.101 seconds)
从配置单元 v0.13.0 开始配置基于 SQL 标准的授权时,不允许使用TRANSFORM
命令。
谢尔德先生
SerDe 代表序列化和反序列化。 它是用于处理记录并将其映射到配置单元表格中的列数据类型的技术。 要解释使用 SerDe 的场景,我们需要首先了解配置单元是如何读写数据的。
读取数据的过程如下。
- 数据从 HDFS 读取。
- 数据由
INPUTFORMAT
实现处理,该实现定义输入数据拆分和键/值记录。 在配置单元中,我们可以使用CREATE TABLE ... STORED AS <FILE_FORMAT>
(参见第 9 章,性能注意事项)来指定它从哪个INPUTFORMAT
读取。 - 调用 SerDe 中定义的 Java
Deserializer
类将数据格式化为映射到表中的列和数据类型的记录。
作为读取数据的示例,我们可以使用 JSON SerDe 从 HDFS 读取TEXTFILE
格式的数据,并将 JSON 属性和值的每一行转换为配置单元表中具有正确模式的行。
写入数据的流程如下:
-
要写入的数据(如使用
INSERT
语句)由 SerDe 中定义的Serializer
类转换为OUTPUTFORMAT
类可以读取的格式。 -
数据由
OUTPUTFORMAT
实现处理,该实现创建RecordWriter
对象。 与INPUTFORMAT
实现类似,OUTPUTFORMAT
实现的指定方式与它写入数据的表的方式相同。 -
数据被写入到表中(保存在 HDFS 中的数据)。
以写入数据为例,我们可以使用 JSON SerDe 将数据行-列写入配置单元表,JSON SerDe 将数据转换为保存到 HDFS 的 JSON 文本字符串。
支持的常用 SerDe(org.apache.hadoop.hive.serde2
)列表如下:
LazySimpleSerDe
:与TEXTFILE
格式一起使用的默认内置 SerDe(org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
)。 其实现方式如下:
> CREATE TABLE test_serde_lz
> STORED as TEXTFILE as
> SELECT name from employee;
No rows affected (32.665 seconds)
ColumnarSerDe
:这是与RCFILE
和ORC
格式一起使用的内置 SerDe。 它的使用方法如下:
> CREATE TABLE test_serde_rc
> STORED as RCFILE as
> SELECT name from employee;
No rows affected (27.187 seconds)
> CREATE TABLE test_serde_orc
> STORED as ORC as
> SELECT name from employee;
No rows affected (24.087 seconds)
RegexSerDe
:这是 SerDe 中用于解析文本文件的内置 Java 正则表达式。 它的使用方法如下:
> CREATE TABLE test_serde_rex(
> name string,
> gender string,
> age string.
> )
> ROW FORMAT SERDE
> 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
> WITH SERDEPROPERTIES(
> 'input.regex' = '([^,]*),([^,]*),([^,]*)',
> 'output.format.string' = '%1$s %2$s %3$s'
> )
> STORED AS TEXTFILE;
No rows affected (0.266 seconds)
HBaseSerDe
:这是内置的 SerDe,使配置单元能够与 HBase 集成。 通过利用该 SerDe 查询和插入数据,我们可以将配置单元表映射到现有的 HBase 表。 在运行以下查询之前,请确保 HBase 守护进程正在运行。 有关更多详细信息,请参阅第 1o 章和使用其他工具:
> CREATE TABLE test_serde_hb(
> id string,
> name string,
> gender string,
> age string
> )
> ROW FORMAT SERDE
> 'org.apache.hadoop.hive.hbase.HBaseSerDe'
> STORED BY
> 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
> WITH SERDEPROPERTIES (
> "hbase.columns.mapping"=
> ":key,info:name,info:gender,info:age"
> )
> TBLPROPERTIES("hbase.table.name" = "test_serde");
No rows affected (0.387 seconds)
AvroSerDe
:这是内置的 SerDe,可以读取和写入 Hive 表中的 AVRO(参见http://avro.apache.org/)数据。 AVRO 是一个远程过程调用和数据序列化框架。 从配置单元 v0.14.0 开始,只需通过三种方式将文件格式指定为AVRO
,即可创建 Avro 支持的表格:
> CREATE TABLE test_serde_avro( -- Specify schema directly
> name string,
> gender string,
> age string
> )
> STORED as AVRO;
No rows affected (0.31 seconds)
> CREATE TABLE test_serde_avro2 -- Specify schema from properties
> STORED as AVRO
> TBLPROPERTIES (
> 'avro.schema.literal'='{
> "type":"record",
> "name":"user",
> "fields":[
> {"name":"name", "type":"string"},
> {"name":"gender", "type":"string", "aliases":["gender"]},
> {"name":"age", "type":"string", "default":"null"}
> ]
> }'
> );
No rows affected (0.41 seconds)
-- Using schema file directly as follows is a more flexiable way
> CREATE TABLE test_serde_avro3 -- Specify schema from schema
file
> STORED as AVRO
> TBLPROPERTIES (
> 'avro.schema.url'='/tmp/schema/test_avro_schema.avsc'
> );
No rows affected (0.21 seconds)
-- Check the schema file
$ cat /tmp/schema/test_avro_schema.avsc
{
"type" : "record",
"name" : "test",
"fields" : [
{"name":"name", "type":"string"},
{"name":"gender", "type":"string", "aliases":["gender"]},
{"name":"age", "type":"string", "default":"null"}
]
}
ParquetHiveSerDe
:这是内置 SerDe(parquet.hive.serde.ParquetHiveSerDe
),支持从配置单元 v0.13.0 开始读取和写入拼花数据格式。 它的使用方法如下:
CREATE TABLE test_serde_parquet
> STORED as PARQUET as
> SELECT name from employee;
No rows affected (34.079 seconds)
OpenCSVSerDe
:这是读写 CSV 数据的串口。 从配置单元 v0.14.0 开始,它是内置的 SerDe。OpenCSVSerDe
比支持转义和引号规范等支持的内置行分隔符更强大。 它的使用方法如下:
> CREATE TABLE test_serde_csv(
> name string,
> gender string,
> age string
>)
> ROW FORMAT SERDE
> 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
> WITH SERDEPROPERTIES (
> "separatorChar" = "\t",
> "quoteChar" = "'",
> "escapeChar" = "\\"
> )
> STORED AS TEXTFILE;
JSONSerDe
:从配置单元 v0.12.0 开始,JSON SerDe 可用于使用配置单元读取和写入 JSON 数据记录:
> CREATE TABLE test_serde_js(
> name string,
> gender string,
> age string
> )
> ROW FORMAT SERDE
> 'org.apache.hive.hcatalog.data.JsonSerDe'
> STORED AS TEXTFILE;
No rows affected (0.245 seconds)
HIVE 还允许用户定义自定义 SerDe(如果所有这些都不适用于他们的数据格式)。 有关自定义 SerDe 的更多信息,请参阅位于https://cwiki.apache.org/confluence/display/Hive/DeveloperGuide#DeveloperGuide-HowtoWriteYourOwnSerDe的配置单元维基。
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,我们介绍了扩展 Hive 功能的四个主要方面。 我们还介绍了三种用户定义函数,以及它们的编码模板和部署步骤,以指导编码和部署过程。 然后介绍了 HPL/SQL,它在 HQL 中加入了过程性语言编程。 此外,我们还讨论了流以插入您自己的代码,这些代码不必是 Java 代码。 在本章的最后,我们讨论了在读写数据时可用于解析不同格式的数据文件的 SerDe。 读完本章后,您应该能够编写基本的 UDF 和 HPL/SQL,将代码插入到流中,并在配置单元中使用可用的 SerDe。
在下一章中,我们将讨论安全注意事项。
九、安全考虑
对于大多数开源软件来说,安全是生产发布前需要解决的一个关键领域。 作为 Hadoop 数据的领先类 SQL 界面,配置单元必须确保数据得到安全保护和访问。 因此,Hive 的安全一直被认为是生态系统不可或缺的重要组成部分。 早期版本的配置单元主要依靠 HDFS 来保证安全性。 Hive 的安全性在hiveserver2
发布后逐渐成熟。
本章将讨论以下方面的配置单元安全:
- 验证
- (被)授权 / (被)批准
- 掩码和加密
验证
身份验证是通过获取用户凭据来验证用户身份的过程。 配置单元从hiveserver2
开始提供身份验证。 在旧版本的配置单元中,hiveserver1
不支持储蓄客户端的 Kerberos 身份验证。 因此,如果我们可以通过网络访问主机/端口,我们就可以访问服务器。 相反,我们可以利用支持 Kerberos 的metastore
服务器进行身份验证。 在本节中,我们将简要介绍metastore
服务器和hiveserver2
服务器中的身份验证配置。
Kerberos is a network authentication protocol developed by MIT as part of Project Athena. It uses time-sensitive tickets that are generated using symmetric key cryptography to securely authenticate a user in an unsecured network environment. Kerberos, in Greek mythology, was the three-headed dog that guarded the gates of Hades. The three-headed part refers to the three parties involved in the Kerberos authentication process: client, server, and Key Distribution Center (KDC). All clients and servers registered to KDC are known as a realm, which is typically the domain's DNS name in all caps. For more information, please refer to the MIT Kerberos website: http://web.mit.edu/kerberos/.
元存储区身份验证
要强制客户端使用 Kerberos 向metastore
服务器进行身份验证,我们可以在hive-site.xml
文件中设置以下三个属性,然后重新启动metastore
服务器以使其正常工作:
- 启用简单身份验证和安全层(SASL)框架以实施客户端 Kerberos 身份验证,如下所示:
<property>
<name>hive.metastore.sasl.enabled</name>
<value>true</value>
<description>If true, the metastore thrift interface will be
secured with SASL framework. Clients must authenticate with
Kerberos.</description>
</property>
- 指定生成的 Kerberos 密钥表。 如果要将文件保存在其他位置,请覆盖以下示例。 确保密钥表文件权限掩码设置为只读权限(600),以避免意外更改或删除。 它还应该属于用于运行
metastore
服务器的同一帐户(默认情况下为配置单元):
<property>
<name>hive.metastore.kerberos.keytab.file</name>
<value>/etc/hive/conf/hive.keytab</value>
<description>The sample path to the Kerberos Keytab file
containing the metastore thrift server's service principal.
</description>
</property>
- 指定 Kerberos 主体模式字符串。 特殊字符串将自动替换为正确的主机名。
YOUR-REALM.COM
值应替换为实际的域名:
<property>
<name>hive.metastore.kerberos.principal</name>
<value>hive/_HOST@YOUR-REALM.COM</value>
<description>The service principal for metastore server.
</description>
</property>
Hiveserver2 身份验证
hiveserver2
支持多种身份验证模式,如 Kerberos、LDAP、PAM 和自定义代码。 要将hiveserver2
配置为使用这些身份验证模式之一,我们可以在hive_site.xml
中设置正确的属性,如下所示,然后重新启动hiveserver2
服务以使其工作:
NONE
:默认设置中为无身份验证。None
这里的意思是允许使用以下设置进行匿名访问:
<property>
<name>hive.server2.authentication</name>
<value>NONE</value>
</property>
KERBEROS
:如果使用 Kerberos 身份验证,则用于在储蓄客户端与hiveserver2
和hiveserver2
之间进行身份验证,并保护 HDFS。 要为hiveserver2
启用 Kerberos 身份验证,我们可以通过指定keytab
路径并在YOUR-REALM.COM
中指定实际域名来设置以下属性:
<property>
<name>hive.server2.authentication</name>
<value>KERBEROS</value>
</property>
<property>
<name>hive.server2.authentication.kerberos.keytab</name>
<value>/etc/hive/conf/hive.keytab</value>
</property>
<property>
<name>hive.server2.authentication.kerberos.principal</name>
<value>hive/_HOST@YOUR-REALM.COM</value>
</property>
Once Kerberos is enabled, the JDBC client (such as Beeline) must include the principal parameter in the JDBC connection string, such as jdbc:hive2://hiveserver2host:10000/default;principal=hive/_HOST@REALM
. For more examples of the supported connection string syntax, refer to https://community.hortonworks.com/articles/4103/hiveserver2-jdbc-connection-url-examples.html.
LDAP
:要将hiveserver2
配置为使用LDAP
支持的用户和密码验证(请参阅https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol),我们可以设置以下属性:
<property>
<name>hive.server2.authentication</name>
<value>LDAP</value>
</property>
<property>
<name>hive.server2.authentication.ldap.url</name>
<value>LDAP_URL, such as ldap://ldaphost@company.com</value>
</property>
<property>
<name>hive.server2.authentication.ldap.Domain</name>
<value>Domain Name</value>
</property>
要使用OpenLDAP
(https://en.wikipedia.org/wiki/OpenLDAP)对其进行配置,我们可以添加baseDN
设置,而不是前面的Domain
属性,如下所示:
<property>
<name>hive.server2.authentication.ldap.baseDN</name>
<value>LDAP_BaseDN, such as ou=people,dc=packtpub,dc=com</value>
</property>
CUSTOM
:这表示hiveserver2
的自定义身份验证提供程序。 要启用它,请按如下方式配置设置:
<property>
<name>hive.server2.authentication</name>
<value>CUSTOM</value>
</property>
<property>
<name>hive.server2.custom.authentication.class</name>
<value>pluggable-auth-class-name</value>
<description>Customized authentication class name, such as
com.packtpub.hive.essentials.hiveudf.customAuthenticator
</description>
</property>
Pluggable authentication with a customized class did not work until the bug (see https://issues.apache.org/jira/browse/HIVE-4778) was fixed in Hive v0.13.0.
以下是实现org.apache.hive.service.auth.PasswdAuthenticationProvider
接口的自定义类的示例。 被覆盖的Authenticate(...)
方法具有如何验证用户名和密码的核心逻辑。 确保将编译的JAR
文件复制到$HIVE_HOME/lib/
,以便前面的设置可以工作:
*// customAuthenticator.java*
package com.packtpub.hive.essentials.hiveudf;
import java.util.Hashtable;
import javax.security.sasl.AuthenticationException;
import org.apache.hive.service.auth.PasswdAuthenticationProvider;
/*
* *The customized class for hiveserver2 authentication*
*/
public class customAuthenticator implements PasswdAuthenticationProvider {
Hashtable<String, String> authHashTable = null;
public customAuthenticator () {
authHashTable = new Hashtable<String, String>();
authHashTable.put("user1", "passwd1");
authHashTable.put("user2", "passwd2");
}
@Override
public void Authenticate(String user, String password)
throws AuthenticationException {
String storedPasswd = authHashTable.get(user);
if (storedPasswd != null && storedPasswd.equals(password))
return;
throw new AuthenticationException(
"customAuthenticator Exception: Invalid user");
}
}
PAM
:从配置单元 v0.13.0 开始,配置单元支持PAM(可插拔身份验证模块)身份验证,这提供了将现有身份验证机制插入到配置单元的好处。 配置以下设置以启用 PAM 身份验证。 有关如何安装 PAM 的更多信息,请参阅位于https://cwiki.apache.org/confluence/display/Hive/Setting+Up+HiveServer2#SettingUpHiveServer2-PluggableAuthenticationModules(PAM)的配置单元维基中的设置**hiveserver2 和文章。
<property>
<name>hive.server2.authentication</name>
<value>PAM</value>
</property>
<property>
<name>hive.server2.authentication.pam.services</name>
<value>pluggable-auth-class-name</value>
<description> Set this to a list of comma-separated PAM servicesthat
will be used. Note that a file with the same name as the PAMservice
must exist in /etc/pam.d.</description>
</property>
(被)授权 / (被)批准
授权用于验证用户是否有权执行特定操作,如创建、读取或写入数据或元数据。 HIVE 提供三种授权模式:传统模式、基于存储的模式和基于 SQL 标准的模式。
传统模式
这是配置单元中的默认授权模式,通过 HQL 语句提供列级和行级授权。 但是,它不是完全安全的授权模式,并且有几个限制。 它主要用于防止好用户意外做坏事,而不是防止恶意用户操作。 为了启用传统授权模式,我们需要在hive-site.xml
中设置以下属性:
<property>
<name>hive.security.authorization.enabled</name>
<value>true</value>
<description>enables or disable the hive client authorization
</description>
</property>
<property>
<name>hive.security.authorization.createtable.owner.grants</name>
<value>ALL</value>
<description>the privileges automatically granted to the owner whenever a table gets created. An example like "select, drop" will grant select and drop privilege to the owner of the table.
</description>
</property>
由于这不是一种安全的授权模式,我们在此不再详细讨论。 有关在传统授权模式下对 HQL 的更多支持,请参阅位于https://cwiki.apache.org/confluence/display/Hive/Hive+Default+Authorization+-+Legacy+Mode的配置单元维基。
基于存储的模式
基于存储的授权模式(从配置单元 v0.10.0 开始)依赖于存储层 HDFS 提供的授权,存储层 HDFS 同时提供 POSIX 和 ACL 权限(从配置单元 v0.14.0 开始提供;请参阅https://issues.apache.org/jira/browse/HIVE-7583)。 在metastore
服务器中启用了基于存储的授权;它具有跨生态系统中其他应用的元数据的单一一致视图。 此模式根据 HDFS 中相应文件目录上的 POSIX 权限检查用户权限。 除了 POSIX 权限模型,HDFS 还提供了访问控制列表,这些列表在位于http://hadoop.apache.org/docs/r2.4.0/hadoop-project-dist/hadoop-hdfs/HdfsPermissionsGuide.html#ACLs_Access_Control_Lists的 HDFS 上的 ACL 中描述。
考虑到其实现,基于存储的授权模式只提供数据库、表和分区级别的授权,而不是列和行级别的授权。 由于依赖于 HDFS 权限,它缺乏通过 HQL 语句管理授权的灵活性。 要启用基于存储的授权模式,我们可以在hive-site.xml
文件中设置以下属性:
<property>
<name>hive.security.authorization.enabled</name>
<value>true</value>
<description>enable or disable the hive client authorization
</description>
</property>
</property>
<name>hive.metastore.pre.event.listeners</name>
<value>org.apache.hadoop.hive.ql.security.authorization.AuthorizationPreEventListener</value>
<description>This turns on metastore-side security.</description>
</property>
<property>
<name>hive.security.authorization.manager</name> <value>org.apache.hadoop.hive.ql.security.authorization.StorageBasedAuthorizationProvider</value>
<description>The class name of the Hive client authorization manager.</description>
</property>
<property>
<name>hive.security.metastore.authorization.manager</name> <value>org.apache.hadoop.hive.ql.security.HadoopDefaultMetastoreAuthenticator
</value>
<description>authenticator manager class name to be used in the metastore for authentication.</description>
</property>
<property>
<name>hive.security.metastore.authorization.auth.reads</name>
<value>true</value>
<description>If this is true, metastore authorizer authorizes read actions on database, table</description>
</property>
With effect from Hive v0.14.0, storage-based authorization also authorizes read privileges on databases and tables by default through the hive.security.metastore.authorization.auth.reads
property. For more information, please refer to https://issues.apache.org/jira/browse/HIVE-8221.
基于 SQL 标准的模式
对于列和行级别的细粒度访问控制,我们可以使用从 Hiev0.13.0 开始提供的基于 SQL 标准的模式。 它类似于使用GRANT
和REVOKE
语句通过hiveserver2
配置控制访问的关系数据库授权。 但是,诸如配置单元或 HDFS 命令之类的工具不能通过hiveserver2
访问数据,因此基于 SQL 标准的模式无法授权它们访问。
因此,建议您结合使用基于存储的模式和基于 SQL 标准的模式,对从各种工具连接的用户进行授权。 要启用基于 SQL 标准的模式授权,我们可以在hive-site.xml
文件中设置以下属性:
<property>
<name>hive.security.authorization.enabled</name>
<value>true</value>
<description>enable or disable the hive client authorization </description>
</property>
<property>
<name>hive.server2.enable.doAs</name>
<value>false</value>
<description>Allows Hive queries to be run by the user who submits the query rather than the hive user. Need to turn if off for this SQL standard-base mode</description>
</property>
<property>
<name>hive.users.in.admin.role</name>
<value>dayongd,administrator</value>
<description>Comma-separated list of users assigned to the ADMIN role.</description>
</property>
<property>
<name>hive.security.authorization.manager</name> <value>org.apache.hadoop.hive.ql.security.authorization.plugin.sql</value> </property>
<property>
<name>hive.security.authenticator.manager</name> <value>org.apache.hadoop.hive.ql.security.authorization.plugin.sqlstd.SQLStdConfOnlyAuthorizerFactory</value>
</property>
<property>
<name>hive.security.metastore.authorization.manager</name>
<value>org.apache.hadoop.hive.ql.security.authorization.StorageBasedAuthorizationProvider,org.apache.hadoop.hive.ql.security.authorization.MetaStoreAuthzAPIAuthorizerEmbedOnly</value>
<description>It takes a comma separated list, so we can add MetaStoreAuthzAPIAuthorizerEmbedOnly along with StorageBasedAuthorization parameter,if we want to enable that as well</description>
</property>
此外,在重启hiveserver2
之前,我们需要在hiveserver2-site.xml
中设置以下配置,以使基于 SQL 标准的授权生效:
<configuration>
<property>
<name>hive.security.authorization.enabled</name>
<value>true</value>
<description></description>
</property>
<property>
<name>hive.security.authorization.manager</name
<value>org.apache.hadoop.hive.ql.security.authorization.plugin.sqlstd.SQLStdHiveAuthorizerFactory</value>
</property>
<property>
<name>hive.security.authenticator.manager</name>
<value>org.apache.hadoop.hive.ql.security.SessionStateUserAuthenticator</value>
</property>
<property>
<name>hive.metastore.uris</name>
<value>thrift://localhost:9085</value>
<description>Use 9083 for hive1 and 9085 for hive2</description>
</property>
</configuration>
Before restarting hiveserver2
to enable the preceding setting, do not forget to grant admin roles to the users defined in hive.users.in.admin.role
using GRANT admin TO USER <user_name>
.
使用基于 SQL 标准的模式授权,我们可以在两个级别上管理权限:角色或对象。
授予或撤销角色级别的授权的语法如下:
GRANT <ROLE_NAME> TO <PRINCIPLES> [ WITH ADMIN OPTION]
REVOKE [ADMIN OPTION FOR] <ROLE_NAME> FROM <PRINCIPLES>
这些参数的用法如下:
<ROLE_NAME>
:这是逗号分隔的角色名称<PRINCIPLES>
:这是用户或角色WITH ADMIN OPTION
:这是可选的。 一旦指定,它将确保用户获得将该角色授予其他用户/角色的权限
另一方面,在对象级别授予或撤销授权的语法如下:
GRANT <PRIVILEGE> ON <OBJECT> TO <PRINCIPLES>
REVOKE <PRIVILEGE> ON <OBJECT> FROM <PRINCIPLES>
这里使用的参数如下:
<PRIVILEGE>
:可以是INSERT
、SELECT
、UPDATE
、DELETE
或ALL
<PRINCIPLES>
:这可以是用户或角色<OBJECT>
:这是一个表或视图
有关管理基于 sql 标准授权的 hql 语句的更多示例,请参考位于https://cwiki.apache.org/confluence/display/Hive/SQL+Standard+Based+Hive+Authorization#SQLStandardBasedHiveAuthorization-Configuration的配置单元维基。
Apache Sentry is a highly modular system for providing centralized, fine-grained, role-based authorization to both data and metadata stored on an Apache Hadoop cluster. It can be integrated with Hive to deliver advanced authorization controls. For more information about Sentry, please refer to https://sentry.apache.org/. Sentry is usually distributed in the Cloudera CDH package. Another similar project is Apache Ranger (https://ranger.apache.org/), which is usually distributed in the Hortonworks HDP package.
掩码和加密
对于敏感和受法律保护的数据,如个人身份信息(PII)或个人机密信息(PCI),需要在文件系统中以加密或屏蔽格式存储数据。 从 Hive v0.13.0 开始,其数据安全功能在数据散列、数据掩蔽、安全和数据加解密功能等领域已经成熟。
数据散列函数
在支持屏蔽数据之前,自 Hieve v1.3.0 以来,内置散列函数一直是一种选择。 散列函数读取输入字符串并生成固定大小的字母数字输出字符串。 由于输出通常是到输入字符串的唯一映射(冲突的可能性很小),因此散列值经常用于保护列,列是连接或比较数据的唯一标识符。 内置函数,如md5(...)
、sha1(...)
和sha2(...)
,可用于 HQL 中的数据散列:
> SELECT
> name,
> md5(name) as md5_name, -- 128 bit
> sha1(name) as sha1_name, -- 160 bit
> sha2(name, 256) as sha2_name -- 256 bit
> FROM employee;
+---------+----------------------------------+
| name | md5_name |
+---------+----------------------------------+
| Michael | 3e06fa3927cbdf4e9d93ba4541acce86 |
| Will | 2b80f09163f60ce1774b438e605eb1f9 |
| Shelley | e47e592945f28b3c3891ee9d27ec6b61 |
| Lucy | 80eb0e612760f756547b660c4c71ba7d |
+---------+----------------------------------+
+------------------------------------------+
| sha1_name |
+------------------------------------------+
| f8c38b2167c0ab6d7c720e47c2139428d77d8b6a |
| 3e3e5802bd4cad8e29e144b515307d8204a3202a |
| 2d4cab849437156354d24c9564958e6581711d08 |
| c5c8f32bdf9998e0f692231f4f969085c8dc225b |
+------------------------------------------+
+------------------------------------------------------------------+
| sha2_name |
+------------------------------------------------------------------+
| f089eaef57aba315bc0e1455985c0c8e40c247f073ce1f4c5a1f8ffde8773176 |
| 6cef4ccc1019d6cee6b9cad39d49cabf808ba2e0665d5832b70c44c09c2dfae0 |
| 1e8b342dde7c90cfbc9634c777b6b59388b6a4bd14274adffbfaeed4b329b26e |
| a3fa95a3b95d421c316f1a9b12c88edcc47896705976764d2652425de98f0c4f |
+------------------------------------------------------------------+
4 rows selected (0.344 seconds)
数据屏蔽功能
从配置单元 v2.1.0 开始,数据掩码函数作为内置 UDF 在 SQL 中可用。 对于信用卡号码、银行账号和密码等用户敏感数据,经常需要屏蔽数据。 与散列函数不同的是,SQL 中的 MASK 函数可以指定对部分数据进行屏蔽,这使得您在想要保持部分数据不被屏蔽以便更好地理解时更加灵活。 以下是使用 HQL 中的各种掩码函数的示例:
> SELECT
-- big letter to U, small letter to l, number to #
> mask("Card-0123-4567-8910", "U", "l", "#") as m0,
-- mask first n (4) values where X|x for big/small letter, n for number
> mask_first_n("Card-0123-4567-8910", 4) as m1,
-- mask last n (4) values
> mask_last_n("Card-0123-4567-8910", 4) as m2,
-- mask everthing except first n(4) values
> mask_show_first_n("Card-0123-4567-8910", 4) as m3,
-- mask everthing except last n(4) values
> mask_show_last_n("Card-0123-4567-8910", 4) as m4,
-- return a hash value - sha 256 hex
> mask_hash('Card-0123-4567-8910') as m5
> ;
+-----------------------+-----------------------+------------------------+
| m0 | m1 | m2 |
+-----------------------+-----------------------+------------------------+
| Ulll-####-####-#### | Xxxx-0123-4567-8910 | Card-0123-4567-nnnn |
+-----------------------+-----------------------+------------------------+
+-------------------+-------------------+--------------------------------+
| m3 | m4 | m5 |
+-------------------+-------------------+--------------------------------+
|Card-nnnn-nnnn-nnnn|Xxxx-nnnn-nnnn-8910|f0679e470f380ce5183ba403ec0e7e64|
+-------------------+-------------------+--------------------------------+
1 row selected (0.146 seconds)
数据加密功能
从 hive v1.3.0 开始,提供了aes_encrypt(input string/binary, key string/binary)
和aes_decrypt(input binary, key string/binary)
自定义函数来支持使用 AAES(高级加密标准:Ehttp://en.wikipedia.org/wiki/Advanced_Encryption_Standard)算法进行数据加密和解密,该算法是由比利时密码学家琼·达门和文森特·里曼开发的一种对称的 128 位块数据加密技术。
以下是使用这些函数的示例:
-- 1st para. is value to encryped/decryped
-- 2nd para. is 128 bit (16 Byte) keys
> SELECT
> name,
> aes_encrypt(name,'1234567890123456') as encrypted,
> aes_decrypt(
> aes_encrypt(name,'1234567890123456'),
> '1234567890123456') as decrypted
> FROM employee;
+---------+-------------------------+-----------+
| name | encrypted | decrypted |
+---------+-------------------------+-----------+
| Michael | ��.b��#����-��I | Micheal |
| Will | "�""��r {cgR�%��� | Will |
| Shelley | ��W@�Dm�[-�?� | Shelley |
| Lucy | ��/i���x���L�q~ | Lucy |
+---------+------------------------+------------+
4 rows selected (0.24 seconds)
其他方法
如前所述,我们可以使用 Apache Ranger 或 Sentry 进行列级访问和控制,以实现更精细的安全性。 此外,还有补丁可用于直接在表创建语句上指定列级编码,例如 hive6329c(https://issues.apache.org/jira/browse/HIVE-6329)和 hive7934(https://issues.apache.org/jira/browse/HIVE-7934)。 在存储级别,配置单元还可以利用 HDFS 加密技术(https://issues.apache.org/jira/browse/HDFS-6134),它提供对 HDFS 上数据的透明加密和解密。 如果我们想在 HDFS 中加密整个数据集,它将满足我们的要求。
简略的 / 概括的 / 简易判罪的 / 简易的
在本章中,我们介绍了配置单元安全领域的身份验证、授权、掩码和加密。 我们介绍了metastore
服务器和hiveserver2
服务器中的身份验证。 然后,我们讨论了默认模式授权、基于存储的模式授权和基于 SQL 标准的模式授权。 在本章的最后,我们讨论了在配置单元中应用数据掩码和安全性的各种方法。 读完本章后,您应该能够使用不同的身份验证、授权和数据掩码或安全方法来解决安全问题。
在下一章中,我们将讨论如何将 Hive 与大数据生态系统中的其他工具配合使用。
十、使用其他工具
作为最早也是最流行的 SQL-over-Hadoop 工具之一,Have 在与其他工具配合提供端到端大数据解决方案时有很多用例。 在本章中,我们将讨论配置单元如何与大数据生态系统中的其他工具在以下方面协同工作:
- JDBC/ODBC 连接器
- NoSQL 数据库
- Hue/Ambari Hive 视图
- HCatalog
- 奥兹
- 斯巴克 (人名)
- Hivemall
JDBC/ODBC 连接器
JDBC/ODBC 是配置单元与其他工具配合使用的最常见方式之一。 Hadoop 供应商,如 Cloudera 和 Hortonworks,提供免费的配置单元 jdbc/odbc 驱动程序,以便配置单元可以通过这些驱动程序连接,这些驱动程序可以在https://www.cloudera.com/downloads/connectors/hive/jdbc.html和https://hortonworks.com/downloads/#addons中找到。
我们可以使用这些 JDBC/ODBC 连接器通过以下工具连接配置单元:
- 在第 2 章,设置配置单元环境中提到的命令行实用程序,如 BELINE
- 集成开发环境,如 Oracle SQL Developer,也在第 2 章和设置配置单元环境中提到
- 数据提取、转换、加载和集成工具,如 Talend Open Studio(https://www.talend.com/products/talend-open-studio/)和 Pentaho(https://www.hitachivantara.com/go/pentaho.html)
- 商业智能、报告和可视化工具,如 QlikView(https://www.qlik.com)和 Tableau(https://www.tableau.com)
- 数据分析工具,例如带有 Power Query 加载项的 Microsoft Excel
由于连接器的设置非常简单,请参考特定工具的网站以获取更详细的连接到 Hive 的说明。
NoSQL
HIVE 不仅提供数据查询连接,还可以将其外部表映射到 NoSQL 数据库,如HBase
或 MongoDB,并具有各种存储处理程序。
要映射HBase
中的现有表,配置单元在表创建语句中使用HBaseStorageHandler
类。 创建到现有配置单元HBase
的配置单元外部表格映射的示例如下:
> CREATE TABLE hbase_table_sample(
> id int,
> value1 string,
> value2 string,
> map_value map<string, string>
> )
> STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
> WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:val,cf2:val,cf3")
> TBLPROPERTIES ("hbase.table.name" = "table_name_in_hbase");
在这个特殊的CREATE TABLE
语句中,HBaseStorageHandler
类使用HiveHBaseTableInputFormat
和HiveHBaseTableOutputFormat
委托与HBase
表的交互。 需要hbase.columns.mapping
属性才能按顺序将语句中定义的每个表列映射到HBase
个表列。 例如,ID 按顺序映射到HBase
表的行键为:key
。 有时,如果没有可用作HBase
表的行键的现有列,我们可能需要使用配置单元 UDF 生成适当的行键列。value1
映射到HBase
表的cf1
列族中的val
列。 配置单元MAP
数据类型可用于访问整个列族。 每行可以有一组不同的列,其中列名对应于映射键,列值对应于映射值,例如map_value
列。 hbase.table.name
属性是可选的,它指定HBase
已知的表名。 如果未提供,则配置单元和HBase
表格将具有相同的名称,如hbase_table_sample
。
通过将HBase
表映射到配置单元,配置单元用户可以将数据插入到HBase
表中,将配置单元表与HBase
表连接,并直接从HBase
查询数据。有关配置单元-HBase
集成正在进行的配置和功能的更多信息,请参阅配置单元维基:https://cwiki.apache.org/confluence/display/Hive/HBaseIntegration。
使用相同的机制,配置单元可以将其外部表映射到 MongoDB(NoSQL)中的集合,MongoDB 是一种流行的文档 https://www.mongodb.com 数据库。 要进行设置,我们需要从https://github.com/mongodb/mongo-hadoop或https://mvnrepository.com/artifact/org.mongodb.mongo-hadoop/mongo-hadoop-core/2.0.2下载 MongoDB 存储处理程序JAR
。 然后在 HQL 中创建表,如下所示,以将其映射到 MongoDB 中的default
数据库中的 nmongo_sample
集合。 请确保在此操作之前启动了 MongoDB:
> ADD JAR mongo-hadoop-core-2.0.2.jar;
> CREATE TABLE mongodb_table_sample(
> id int,
> value1 string,
> value2 string
> )
> STORED BY 'com.mongodb.hadoop.hive.MongoStorageHandler'
> WITH SERDEPROPERTIES (
> 'mongo.columns.mapping'='{"id":"_id","value1":"value1","value2":"value2"}')
> TBLPROPERTIES(
> 'mongo.uri'='mongodb://localhost:27017/default.mongo_sample'
> );
之后,我们可以像在HBase
映射表中那样插入或查询数据。 从 Hiev2.3.0 开始,提供了一个更通用的 JDBC 驱动程序存储处理程序,使配置单元表映射到大多数 JDBC 兼容数据库中的表。 有关详细信息,请参阅 HIVE-1555(https://issues.apache.org/jira/browse/HIVE-1555)。
Hue/Ambari Hive 视图
Hue(hadoop)是 http://gethue.com/用户体验的缩写。 它是一个使 Hadoop 生态系统更易于使用的 Web 界面。 对于配置单元用户,它提供了统一的 Web 界面,以便在交互环境中轻松访问 HDFS 和配置单元。 Hue 默认安装在 CDH 中,也可以安装在其他 Hadoop 发行版中。 此外,Hue 还为配置单元添加了更多编程友好的功能,例如:
- 突出显示 HQL 关键字
- 自动完成 HQL 查询
- 提供配置单元和 MapReduce 作业的实时进度和日志
- 稍后提交多个查询并检查进度
- 通过 Web 用户界面浏览配置单元表中的数据
- 在元数据中导航
- 通过 Web 用户界面注册 UDF 并添加文件/归档
- 保存、导出和共享查询结果
- 根据查询结果创建各种图表
以下是以色调显示的配置单元编辑器界面的屏幕截图:
Hue Hive editor user interface
另一方面,开源 Hadoop 集群管理工具 Ambari 提供了另一个配置单元图形 Web 用户界面,配置单元视图(最新版本 2)。 在浏览器中执行以下功能时,它可为分析师和 DBA 提供更好的用户体验:
- 浏览数据库和表
- 以全屏模式编写查询或浏览查询结果
- 管理查询执行作业和历史记录
- 查看现有数据库、表及其统计信息
- 创建表并将表 DDL 导出到源代码管理
- 查看可视化解释计划
以下是安巴里 Hive 视图版本 2 的屏幕截图:
Ambari Hive view 2
HCatalog
HCatalog(参见Hadoop)是一个针对 https://cwiki.apache.org/confluence/display/Hive/HCatalog 数据的元数据管理系统。 它为 Hadoop 生态系统工具(如 Pig、Have 和 MapReduce)存储一致的架构信息。 默认情况下,HCatalog 支持RCFile
、CSV
、JSON
、SequenceFile
、ORC
文件格式的数据,如果实现了InputFormat
、OutputFormat
和SerDe
,则支持自定义格式。 通过使用 HCatalog,用户能够直接创建、编辑和公开(通过其 rest API)元数据,这在共享相同元数据的所有工具中立即生效。 起初,HCatalog 是一个独立于配置单元的 Apache 项目。 最终,HCatalog 在 2013 年成为 Hive 项目的一部分,从 Hive v0.11.0 开始。 HCatalog 构建在Hive metastore
之上,并结合了对 HQL DDL 的支持。 它提供读写接口以及HCatLoader
和HCatStorer
。 对于 Pig,它实现了 Pig 的 Load 和 Store 接口。 HCatalog 还通过使用与其他定制格式非常相似的HCatInputFormat
和HCatOutputFormat
,通过实现 Hadoop 的InputFormat
和OutputFormat
,为 MapReduce 程序提供了一个接口。
此外,HCatalog 还从一个名为 WebHCat 的组件提供了 REST API,以便可以从其他应用发出 HTTP 请求,以通过 HCatalog 访问 Hadoop MapReduce/Yarn、Pig 和 Have 的元数据。 由于 HCatalog 使用配置单元的metastore
,因此没有特定于配置单元的 REST 接口。 因此,HCatalog 可以直接通过其 CLI 定义配置单元的元数据。 HCatalog CLI 支持 HQLSHOW
/DESCRIBE
语句和大多数配置单元 DDL,以下语句除外,这些语句需要触发 MapReduce 作业:
** CREATE TABLE ... AS SELECT
ALTER INDEX ... REBUILD
ALTER TABLE ... CONCATENATE
ALTER TABLE ARCHIVE/UNARCHIVE PARTITION
ANALYZE TABLE ... COMPUTE STATISTICS
IMPORT/EXPORT
奥兹
Oozie(http://oozie.apache.org/)是一个开源的工作流协调和调度服务,用于管理数据处理作业。 Oozie 工作流作业在有向循环图(DAG)中的一系列节点中定义。 在这里,无循环意味着图中没有循环,图中的所有节点都向一个方向流动,而不会返回。 Oozie 工作流包含控制流节点或操作节点:
- 控制流节点:它定义工作流中的开始、结束和失败节点,或者控制工作流执行路径,如决策、分叉和联接节点
- Action node:这定义了核心数据处理操作作业,比如 MapReduce、Hadoop 文件系统、Hive、Pig、Spark、Java、Shell、Email 和 Oozie 子工作流。 自定义扩展模块还支持其他类型的操作
Oozie 是一个可伸缩、可靠和可扩展的系统。 它可以针对工作流提交进行参数化,并安排为自动运行。 因此,Oozie 非常适合于轻量级数据集成或维护工作。 核心 Oozie 作业需要一个工作流定义 XML 文件和一个属性文件。 以下是使用hive2
操作提交查询的工作流 XML 文件示例。 工作流 XML 文件应上载到 HDFS 才能提交作业:
<!-- This is Oozie workflow definition -->
<workflow-app name="hive2-wf">
<start to="hive2-node"/>
<action name="hive2-node">
<hive2 >
<job-tracker>${jobTracker}</job-tracker>
<name-node>${nameNode}</name-node>
<configuration>
<property>
<name>mapred.job.queue.name</name>
<value>${queueName}</value>
</property>
</configuration>
<!-- the hiveserver2 jdbc uri from property file -->
<jdbc-url>${jdbcURL}</jdbc-url>
<!-- the hdfs path for the hql -->
<script>/tmp/hql_script.hql</script>
<!-- pass parameters to the hql -->
<param>database=${database}</param>
</hive2>
<ok to="end"/>
<error to="fail"/>
</action>
<kill name="fail">
<message>Failed for [${wf:errorMessage(wf:lastErrorNode())}]
</message>
</kill>
<end name="end"/>
</workflow-app>
以下是工作流的作业属性文件。 属性文件应保存在本地:
$ cat job.properties
nameNode=hdfs://localhost:8020
jobTracker=localhost:8032
queueName=default
examplesRoot=examples
jdbcURL=jdbc:hive2://localhost:10000/default
database=default
oozie.use.system.libpath=true
oozie.wf.application.path=${nameNode}/user/${user.name}/${examplesRoot}/apps/hive2
我们可以将workflow.xml
文件上传到在oozie.wf.application.path
属性中定义的 HDFS 位置。 然后,运行以下命令提交作业并获取作业 ID 以进行作业管理或监视:
$ export OOZIE_URL=http://localhost:11000/oozie
$ oozie job -run -config job.properties
job: 0000001-161213015814745-oozie-oozi-W
斯巴克 (人名)
作为一个通用的数据引擎,Apache Spark 可以与 Hive 紧密结合。 Spark SQL 支持 HQL 的一个子集,可以利用Hive metastore
在配置单元中写入或查询数据。 这种方法也称为 Hive 上的火花。 要配置 Spark,请使用配置单元metastore
,您只需将hive-site.xml
复制到${SPARK_HOME}/conf
目录。 之后,运行 Sparkspark-sql
命令将进入 Spark SQL 交互环境,在该环境中可以编写 SQL 来查询配置单元表。
另一方面,Spark 之上的 Hive 是一种类似的方法,但它允许 Hive 使用 Spark 作为替代引擎。 在这种情况下,用户仍然留在配置单元中并编写 HQL,但会透明地运行 Spark 引擎。 星火之上的 Hive 需要 YarnFairScheduler
和set hive.execution.engine=spark
。 有关更多详细信息,请参阅https://cwiki.apache.org/confluence/display/Hive/Hive+on+Spark%3A+Getting+Started。
Hivemall
Apache Hivemall(https://hivemall.incubator.apache.org/)是一组用于机器学习的配置单元 UDF。 它包含许多跨分类、回归、推荐、损失函数和特征工程的 ML 算法实现,所有这些都是 UDF。 这允许最终用户使用 SQL,并且只允许 SQL 将机器学习算法应用于大量的训练数据。 执行以下步骤进行设置:
- 从https://hivemall.incubator.apache.org/download.html下载 Hivemall,放入 hdfs:
$ hdfs fs -mkdir -p /apps/hivemall
$ hdfs fs -put hivemall-all-xxx.jar /apps/hivemall
- 在此处(https://github.com/apache/incubator-hivemall/blob/master/resources/ddl/define-all-as-permanent.hive)使用脚本创建永久函数:
> CREATE DATABASE IF NOT EXISTS hivemall; -- create a db for the
udfs
> USE hivemall;
> SET hivevar:hivemall_jar=
> hdfs:///apps/hivemall/hivemall-all-xxx.jar;
> SOURCE define-all-as-permanent.hive;
- 验证是否已创建函数:
> SHOW functions "hivemall.*";
hivemall.adadelta
hivemall.adagrad
...
简略的 / 概括的 / 简易判罪的 / 简易的
在这最后一章中,我们从配置单元 JDBC 和 ODBC 连接器开始。 然后,我们介绍了其他常用的大数据工具和库,比如 NoSQL(HBase、MongoDB)、Web 用户界面(Hue、Ambari Have View)、HCatalog、Oozie、Spark 和 Hivemall。 阅读完本章后,您现在应该了解如何将其他大数据工具与 Hive 配合使用,以提供端到端的数据智能解决方案。*