单元7 Hive性能优化
存储、参数、语句三条线,把“能跑”变成“跑得快”
本单元学习目标 摘自课件“学习目标/Target”页
- 掌握存储优化,能够灵活运用不同文件格式和压缩方式的组合提高数据存储效率与查询性能。
- 了解参数优化,能够通过调整Hive参数配置优化查询性能。
- 熟悉HiveQL语句优化技巧,能够通过合理编写HiveQL语句提升查询效率。
单元概述
在实际的大数据分析中,Hive的执行性能不仅依赖底层硬件资源和执行引擎,还与数据存储方式、参数配置以及HiveQL语句的编写习惯密切相关。合理的存储方式能够减少I/O开销、提升查询效率;针对性的参数调整可以在性能、稳定性与资源利用率之间取得平衡;而高效的HiveQL语句则能避免冗余计算,在不改动底层配置的情况下显著加快查询执行。本单元将从这3个方面介绍Hive的优化。
知识地图
学完本单元,你应该能回答
- 小文件为什么拖慢 Hive?如何合并?
- 数据倾斜的典型表现与破解手段?
- 哪些参数真正影响并行度与内存?
- 谓词下推、向量化、CBO 各自优化了什么?
知识链位置
核心知识 点击标题右侧勾选框可标记“已掌握”,进度会保存在本机浏览器
优化数据存储
掌握存储优化,能够灵活运用不同文件格式和压缩方式的组合提高数据存储效率与查询性能。
Hive的数据存储本质上依赖于HDFS,所有数据的读写都基于HDFS文件完成。为提升文件的读写性能,Hive提供了多种存储格式,不同文件格式在存储效率与查询性能上的差异,直接决定了后续优化空间的大小。基于此,在建表时选择合适的文件格式就成为Hive优化的重要起点。不过,单靠文件格式往往难以达到最佳效果,还需要结合合适的压缩方式,以进一步降低存储空间、减少I/O开销,从而显著提升资源利用率和查询性能。
下面将分别介绍Hive中常用的文件格式和压缩方式,具体内容如下。
压缩
方式
文件
格式
Hive常用的文件格式包括TEXTFILE、ORC和PARQUET,其中TEXTFILE具有良好的可读性和兼容性,但压缩率较低、查询性能较弱,通常适用于小规模数据分析或数据导入场景。ORC为大数据处理优化设计,支持高效压缩与快速查询,是Hive官方推荐的文件格式。PARQUET与ORC类似,同样采用列式存储,但更注重跨平台兼容性,在不同大数据组件之间具有更好的通用性。
1. 常用的文件格式
Hive常用的压缩方式包括Snappy、zlib、LZO和Zstandard(Zstd),每种压缩方式具有不同的特点,具体介绍如下。
2. 常用的压缩格式
LZO的压缩与解压速度较快,压缩率介于Snappy与zlib之间,适合大文件的并行处理。
Snappy的压缩与解压速度快,可显著提升读写性能,但压缩率适中,适用于对查询性能要求较高的场景。
zlib的压缩率高,但压缩与解压速度较慢,适用于以节省存储空间为主要目标的场景。
Zstandard的压缩率通常优于zlib,解压速度接近Snappy,但压缩速度略低于Snappy,兼顾压缩率与性能。
例如,在Hive中创建文件格式为ORC且压缩方式为Snappy的表ecommerce_orders,具体HiveQL语句如下。
点击查看
任务:优化配置参数
7.2
建表时就选好格式与压缩——这一步决定了后面能优化到什么程度。
CREATE TABLE score_orc(
stu_id STRING, course_id STRING, score INT
)
STORED AS ORC
TBLPROPERTIES ('orc.compress' = 'SNAPPY');
-- 对比不同压缩方式(Hive 常用四种)
-- SNAPPY:快,压缩率中等 | ZLIB:压缩率高,慢
-- LZO:可切分,适合大文件 | ZSTD:压缩率与速度兼顾
SET hive.exec.compress.output = true;
SET mapreduce.output.fileoutputformat.compress.codec = org.apache.hadoop.io.compress.SnappyCodec;💡 列存 + 压缩的效果要按“读了多少列、多少字节”衡量,所以查询只取必要字段才能吃到红利。
📄 展开课件原文(第 6–12 页,共 7 页)
优化配置参数
了解参数优化,能够通过调整Hive参数配置优化查询性能。
Hive的配置文件中包含大量参数,其中大多采用默认值以兼顾常见场景。但在实际应用中,由于数据规模、硬件环境等因素各不相同,仅依赖默认配置往往难以在性能、稳定性和资源利用率之间取得最佳平衡。因此,进行针对性的参数优化就显得尤为关键。参数优化不仅是提升Hive性能的重要手段,也是保障大规模数据分析顺利完成的基础。本任务将介绍Hive中常用的参数优化方法,具体内容如下。
并行执行
分组优化
本地模式
中间数据压缩
参数
优化
1.并行执行
并行执行是指允许多个彼此无依赖关系的任务同时运行,从而缩短整个查询语句的执行时间。例如,表连接前的子查询处理,或多条插入操作中的文件移动,都可以并行执行。
在Hive中,并行执行由参数hive.exec.parallel控制,该参数默认值为false,表示关闭并行执行,此时无依赖关系的任务会依次顺序执行。若要开启并行执行,则需将参数hive.exec.parallel的值设置为true。
如果希望在当前会话中临时开启并行执行,那么可以执行如下HiveQL语句。
SET hive.exec.parallel=true;如果希望永久开启并行执行,那么可以Hive配置文件中添加如下参数。
<property>
<name>hive.exec.parallel</name>
<value>true</value>
</property>本地模式是指允许Hive自动将查询切换为在本地任务中执行,而不是提交到YARN集群,从而减少集群资源的消耗。例如,在处理小规模数据的聚合查询时,默认情况下Hive仍会将任务提交到YARN集群执行。
2.本地模式
在Hive中,本地模式由参数hive.exec.mode.local.auto控制,该参数默认值为false,表示关闭本地模式,此时Hive不会自动切换到本地任务。若要开启本地模式,则需将参数hive.exec.mode.local.auto的值设置为true。
如果希望在当前会话中临时开启本地模式,那么可以执行如下HiveQL语句。
SET hive.exec.mode.local.auto=true;如果希望永久开启本地模式,那么可以在Hive配置文件中添加如下参数。
<name>hive.exec.mode.local.auto</name>在开启本地模式时,Hive会根据以下条件判断是否将查询作为本地任务执行。
聚合阶段的并行任务数小于等于1。
输入数据的总字节数小于等于134217728字节,该条件由参数hive.exec.mode.local.auto.inputbytes.max控制。
输入数据阶段的文件数小于等于4,该条件由参数hive.exec.mode.local.auto.input.files.max控制。
在Hive中执行分组查询时,输入阶段的任务会根据分组字段的值,通过Shuffle将相同分组的数据分发到下游任务中执行。当分组字段的某些取值出现频率远高于其他值时,容易引发数据倾斜,导致部分下游任务的数据量过大。
3.分组优化
为缓解这一问题,可以通过参数hive.groupby.skewindata开启分组优化,该参数的默认值为false,表示关闭分组优化。若要开启分组优化,则需将参数hive.groupby.skewindata的值设置为true。
如果希望在当前会话中临时开启分组优化,那么可以执行如下HiveQL语句。
SET hive.groupby.skewindata=true;如果希望永久开启分组优化,那么可以Hive配置文件中添加如下参数。
<name>hive.groupby.skewindata</name>4.中间数据压缩
中间数据压缩是指在查询过程中,对产生的中间结果使用指定的压缩方式进行压缩。对于体量巨大的中间数据,这种方式能够显著减少磁盘I/O和网络带宽的消耗,但代价是需要占用更多的CPU资源来完成压缩与解压操作。
当Hive使用Tez作为执行引擎时,中间数据压缩主要受以下三个参数控制。
参数tez.runtime.compress.codec用于指定Tez使用的压缩方式,该参数默认值为空,表示不会对中间数据进行压缩。
参数hive.exec.compress.intermediate用于开启Hive层面的中间数据压缩,该参数默认值为false,表示关闭中间数据压缩。
参数tez.runtime.compress用于开启Tez运行时对中间数据进行压缩,该参数默认值为空,表示关闭中间数据压缩。若要开启中间数据压缩,则需将参数tez.runtime.compress的值设置为true。
如果希望在当前会话中临时开启中间数据压缩,那么可以执行下列HiveQL语句。
SET hive.exec.compress.intermediate=true;
SET tez.runtime.compress=true;
SET tez.runtime.compress.codec=org.apache.hadoop.io.compress.SnappyCodec;在Hive配置文件中添加完上述参数之后,还需要在Tez安装目录的conf目录下,新建一个配置文件tez-site.xml,然后在该文件中添加如下内容。
点击查看
任务:优化执行语句
7.3
📄 展开课件原文(第 13–28 页,共 16 页)
优化执行语句
熟悉HiveQL语句优化技巧,能够通过合理编写HiveQL语句提升查询效率。
在Hive的实际应用中,HiveQL语句的书写方式对查询性能有着直接影响。即使底层参数配置合理,如果语句本身存在冗余计算或不合理的逻辑,依旧可能导致作业运行缓慢、资源消耗过大。掌握语句优化技巧,可以在不修改数据存储格式和底层配置的情况下,显著提升查询效率。下面将介绍五种常见的HiveQL语句优化技巧。
利用分区裁剪
避免全字段查询
连接查询中的MapJoin
分组聚合查询的过滤条件
连接查询中的NULL
在创建分区表时,应将查询中过滤频率较高的字段设置为分区字段,以便在执行查询时利用分区裁剪机制,减少数据扫描量、提升查询效率。
Hive默认开启了MapJoin优化策略,该策略在执行连接查询时,会将小表加载到内存中,在扫描大表的过程中完成匹配操作,避免产生大量Shuffle开销,提升查询性能。
在分组聚合查询时,过滤条件应优先放在WHERE子句中,而不是HAVING子句中。
在连接查询中,若不需要NULL参与匹配,应在ON子句中添加过滤条件,将关联字段中的NULL过滤掉。
在执行查询语句时,避免指定查询内容为*,而是仅指定需要的字段,减少I/O和数据传输量。
AI助学
学完本单元后,读者已经初步掌握Hive性能优化的基本方法,能够从存储优化、参数优化和HiveQL语句优化等方面理解提升查询效率的常见思路。然而,在实际分析任务中,查询执行缓慢的原因可能来自文件格式与压缩方式选择不当、参数配置不合理,也可能与HiveQL语句编写方式有关。对于初学者而言,往往难以根据执行现象判断应从哪个方面入手进行排查与优化。此时,可借助AI对查询场景、表结构、参数配置和HiveQL语句进行辅助分析,梳理优化思路,并提示可能存在的性能问题。
AI
助
学
单元总结
本单元主要讲解了Hive优化。首先,介绍了数据存储优化方法;然后,讲解了配置参数优化方法;最后,讲解了执行语句优化方法。通过本单元的学习,读者能够根据业务需求选择合适的优化策略,从存储结构、参数配置及语句编写等方面全面提升Hive的查询性能与资源利用率。
单
元
总
结
📄 展开课件原文(第 29–33 页,共 5 页)
🧪 动手做一遍 把慢查询一步步调快:6 个真实场景
预计用时约 30 分钟 · 每一步都给出可执行语句与预期结果,建议边看边在 Beeline / DataGrip 里敲一遍
本实例步骤
- 01 场景一:小文件太多,查询起一堆 Map
- 02 场景二:不带分区条件,全表扫描
- 03 场景三:数据倾斜,一个任务跑一小时
- 04 场景四:存储格式与压缩选得不对
- 05 场景五:并行度与执行参数没调
- 06 场景六:SQL 写法本身就能优化
01场景一:小文件太多,查询起一堆 Map
每次 INSERT 少量数据都会生成新文件,文件数很快失控。
-- 问题现场:循环小批插入 100 次 → 100+ 个小文件
INSERT INTO score_info VALUES ('099','01',66); -- 反复执行
hdfs dfs -ls /user/hive/warehouse/school.db/score_info | wc -l
-- 解决 1:合并已有小文件(ORC 表可直接 concatenate)
ALTER TABLE score_info CONCATENATE;
-- 解决 2:让后续写入自动合并
SET hive.merge.mapfiles = true; -- Map-only 任务结束合并
SET hive.merge.mapredfiles = true; -- MapReduce 任务结束合并
SET hive.merge.size.per.task = 134217728; -- 合并后目标大小 128MB
SET hive.merge.smallfiles.avgsize = 16777216;-- 平均小于 16MB 就触发合并| 做法 | 效果 |
|---|---|
| 不处理 | 1000 个小文件 → 1000 个 Map 任务,NameNode 压力大 |
| CONCATENATE | HDFS 层面合并,不重写数据,秒级 |
| merge 参数 | 后续写入自动按 128MB 合并,治本 |
INSERT OVERWRITE 重写一遍来合并。02场景二:不带分区条件,全表扫描
分区裁剪是 Hive 最立竿见影的优化。
-- 慢:扫描全部 12 个月的数据
SELECT count(*) FROM visit_dwd WHERE yearinfo = '2025';
-- 快:带上月分区,只扫 7 月一个目录
SELECT count(*) FROM visit_dwd WHERE yearinfo = '2025' AND monthinfo = '07';
-- 用 EXPLAIN 看扫描了几个分区
EXPLAIN SELECT count(*) FROM visit_dwd WHERE yearinfo = '2025' AND monthinfo = '07';| 写法 | 扫描分区数 | 耗时对比 |
|---|---|---|
| WHERE yearinfo='2025' | 12 | 1× |
| + AND monthinfo='07' | 1 | 约 1/12 |
substr(dt,1,4))会让裁剪失效。03场景三:数据倾斜,一个任务跑一小时
某个 key 的数据量远超其它 key,导致个别 Reduce 拖垮整个作业。
-- 问题:course_id='01' 的记录特别多,按它分组就会倾斜
SELECT course_id, count(*) FROM score_info GROUP BY course_id;
-- 解决 A:两阶段聚合(先加随机盐打散,再去盐汇总)
WITH stage1 AS (
SELECT course_id,
cast(rand() * 10 AS INT) AS salt, -- 加盐:0~9
count(*) AS part_cnt
FROM score_info GROUP BY course_id, cast(rand() * 10 AS INT)
)
SELECT course_id, sum(part_cnt) AS cnt
FROM stage1 GROUP BY course_id;
-- 解决 B:小表 join 大表时用 Map Join,避免 Shuffle
SELECT /*+ MAPJOIN(c) */ s.stu_name, c.course_name, sc.score
FROM score_info sc
JOIN course_info c ON sc.course_id = c.course_id;验证语句:SELECT course_id, count(*) AS cnt FROM score_info GROUP BY course_id ORDER BY course_id
| course_id | cnt |
|---|---|
| 01 | 19 |
| 02 | 19 |
| 03 | 19 |
| 04 | 12 |
| 05 | 5 |
SET hive.groupby.skewindata=true(Hive 自动两阶段)、SET hive.auto.convert.join=true(自动 Map Join)。04场景四:存储格式与压缩选得不对
列存 + 压缩能同时省存储和 I/O,是建表时就要定下来的事。
-- 文本格式:可读性好,但压缩率低、查询慢
CREATE TABLE score_text(stu_id STRING, course_id STRING, score INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE;
-- ORC + Snappy:Hive 官方推荐组合,列存 + 高效压缩
CREATE TABLE score_orc(stu_id STRING, course_id STRING, score INT)
STORED AS ORC
TBLPROPERTIES ('orc.compress' = 'SNAPPY');
INSERT INTO score_orc SELECT * FROM score_info;
-- 看两种格式的实际大小
-- hdfs dfs -du -h /user/hive/warehouse/school.db/score_text /user/hive/warehouse/school.db/score_orc| 格式 | 特点 | 适用 |
|---|---|---|
| TEXTFILE | 行存、可读、压缩率低 | 导入中转、小数据量 |
| ORC | 列存、压缩率高、查询快 | Hive 数仓首选 |
| PARQUET | 列存、跨平台好 | 要与 Spark/Impala 共用时 |
| 压缩 | Snappy 快、zlib 压缩率高、Zstd 兼顾 | 按“要速度还是省空间”选 |
SELECT * 会把这个优势抹平——写查询时只取需要的列。05场景五:并行度与执行参数没调
同一份资源,参数不同性能差数倍。
-- 无依赖的任务并行执行(如多个子查询、多次 INSERT)
SET hive.exec.parallel = true;
SET hive.exec.parallel.thread.number = 8;
-- Reduce 数量:太多则小文件多,太少则单个任务慢
SET hive.exec.reducers.bytes.per.reducer = 268435456; -- 每个 Reduce 处理 256MB
SET hive.exec.reducers.max = 1009;
-- 中间结果压缩,减少落盘与网络传输
SET hive.exec.compress.intermediate = true;
SET hive.exec.compress.output = true;
-- 基于代价的优化(CBO)与向量化执行
SET hive.cbo.enable = true;
SET hive.vectorized.execution.enabled = true;
SET hive.vectorized.execution.reduce.enabled = true;| 参数 | 默认 | 作用 |
|---|---|---|
| hive.exec.parallel | false | 无依赖任务并行,缩短总时长 |
| hive.exec.reducers.bytes.per.reducer | 256MB | 控制 Reduce 个数 |
| hive.cbo.enable | true | 基于统计信息选更优执行计划 |
| hive.vectorized.execution.enabled | true | 按批处理列,CPU 效率更高 |
EXPLAIN 和 YARN 界面找到瓶颈(Map 慢 / Reduce 慢 / 倾斜),再针对性调参,不要一次性全开。06场景六:SQL 写法本身就能优化
同样的结果,换一种写法少扫一半数据。
-- 差:先全表关联再过滤
SELECT s.stu_name, sc.score
FROM student_info s JOIN score_info sc ON s.stu_id = sc.stu_id
WHERE sc.course_id = '01';
-- 好:先过滤再关联(谓词下推 + 减少参与 Shuffle 的数据)
SELECT s.stu_name, sc.score
FROM (SELECT stu_id, score FROM score_info WHERE course_id = '01') sc
JOIN student_info s ON s.stu_id = sc.stu_id;
-- 差:用 count(distinct) 一次算多个去重指标
-- 好:先 group by 再 count,减少一次 Shuffle
SELECT course_id, count(*) AS 人数
FROM (SELECT DISTINCT course_id, stu_id FROM score_info) t
GROUP BY course_id;验证语句:SELECT s.stu_name, sc.score FROM student_info s JOIN score_info sc ON s.stu_id = sc.stu_id WHERE sc.course_id = '01' ORDER BY sc.score DESC LIMIT 5
| stu_name | score |
|---|---|
| 彭于晏 | 94 |
| 刘亦菲 | 90 |
| 宋丹丹 | 89 |
| 刘德华 | 85 |
| 于谦 | 84 |
SELECT *、用 UNION ALL 代替 UNION、大表 join 小表走 Map Join、能不用 ORDER BY 就用 SORT BY。