Hive性能调优是个老生常谈的话题,但每次接到新的慢任务,我都发现不少人还在用最原始的方式排查:看到哪个Job慢就调哪个Job,内存不够就加内存,Reduce数不对就硬改参数。结果往往是这次调好了,下次换个数据量又打回原形。我花了不少时间整理了一套从定位、表设计、SQL写法到引擎参数的完整调优思路,用在实际项目里效果比较稳定。这篇就把这套流程和具体操作完整分享出来,适合正在被Hive慢查询折磨、又不想靠玄学改参数的同学参考。
1. 调优前先定位:Hive慢的根源到底在哪里
1.1 性能瓶颈的四个层面
Hive查询慢,表面上是执行时间长,但原因可能分布在完全不同的层面。我习惯把瓶颈分成四层:第一层是集群资源本身,包括CPU、内存、磁盘IO和网络;第二层是Hive表的设计,比如分区粒度、文件大小、存储格式;第三层是SQL写法和执行计划,Join顺序、数据倾斜、谓词下推失效都在这层;第四层是引擎和参数配置,MapReduce、Tez或者Spark的选择,以及各种阈值参数。四层之间互相影响,但很多人一上来就调参数,等于跳过了前两层直接改第四层,完全是在碰运气。
举个例子,我曾经接过一个任务,某个业务表每天写入几万个小于1MB的小文件,查询时Map数飙到几万,光启动任务就花了十几分钟。这种问题你改任何执行参数都没用,根因在文件存储层面。反过来,一个SQL如果用错了Join方式,导致Reduce端数据倾斜,你就算把所有容器内存翻倍,也只是让那个被倾斜的容器从OOM变成勉强能跑,整体时间一点没省。
所以定位永远是第一步。我的做法是:先看任务统计(Job History或运维平台里的Map数、Reduce数、Shuffle量、各阶段耗时),再看表结构和文件情况,最后才看SQL执行计划。看统计信息能快速判断是不是资源分配不合理,比如某个阶段数据量只有几GB但开了几百个Map;看文件情况能判断是不是小文件泛滥;最后用EXPLAIN看执行计划,确认Join方式和过滤条件下推是否生效。
我总结了一个简单的优先级判断:如果任务时间主要花在某个特定Reduce阶段,优先考虑数据倾斜;如果Map数明显多于合理范围,优先考虑小文件和Split大小;如果所有阶段都慢且集群负载高,那就是资源竞争,需要考虑队列配置或者错峰执行。这个判断逻辑用熟了之后,基本五分钟内能锁定问题方向。
1.2 先学会看执行计划:EXPLAIN的用法
很多人在Hive里写SQL,写完直接跑,跑慢了就开始改参数,从来没有用EXPLAIN看过执行计划。这其实是最不应该跳过的环节。EXPLAIN在Hive里对应几条命令:EXPLAIN查看逻辑和物理执行计划的框架,EXPLAIN EXTENDED输出更多细节比如数据源、谓词信息,EXPLAIN COST显示CPU、IO等方面的估算成本,新版里EXPLAIN FORMATTED的输出更接近JSON结构。看起来很复杂,但重点看几个地方就够了。
第一个是TableScan下面的Partition,这里会显示查询实际扫描哪些分区。如果这里显示扫描了全表所有分区,说明你的分区裁剪没生效,SQL里的WHERE条件没有正确转换成分区过滤。这是最典型的“看起来一样但性能差十倍”的场景。第二个是Join Operator的位置和类型。如果看到Map Join,说明小表已经被加载到内存中,在Map端完成了Join;如果看到Reduce Join(也就是Common Join),数据要从两个Map端Shuffle到Reduce端,成本高很多。第三个是Reduce Sink的数量和分布,可以粗看Shuffle量。
看执行计划这个习惯,我建议所有写Hive的人养成。不用完全看懂每个算子,只需要对比优化前后的EXPLAIN输出差异,就能看出改动是否生效。比如你把小表放在Join左边之后,执行计划里会不会从Reduce Join变成Map Join,一目了然。切不可改完参数直接跑,跑完只看时间,因为任务变慢可能只是因为集群当时有别的任务竞争,和你的改动其实无关。
2. 表设计层面的优化,决定了性能上限
2.1 分区策略的设计与实操
Hive表的性能上限,很大程度上在表设计阶段就定死了。设计得好的表,SQL怎么写都不会太慢;设计得差的表,SQL优化到极致也救不回来。分区是最基础也是最重要的手段。分区的本质是让查询只扫描需要的目录,而不是全表扫描。这里的核心问题不是“要不要分区”,而是“按什么维度分区”以及“分区粒度多大”。
我见过不少表按天分区,查询却需要跨两三个月的数据做统计,每次全表扫描几十个分区的数据。这种场景如果能按实际统计口径重新设计,比如增加按月分区,或者把数据改造成宽表加明细表的结构,查询效率完全不一样。分区粒度的原则是:既不能太粗(比如只按年分区),也不能太细(比如按小时分区但每个分区只有几百KB)。太细会导致元数据膨胀、NameNode压力大、任务跑起来全是小文件。比较合理的做法:日均数据量大的表按天分区,数据量小的低频业务表按月分区,极端实时场景才考虑按小时。
另一个经常被忽略的点是动态分区。Hive支持INSERT动态写入分区,默认hive.exec.dynamic.partition.mode是nomove,也就是需要至少一个静态分区前缀。如果所有分区都是动态的,必须设置为nonstrict。同时要注意hive.exec.max.dynamic.partitions.pernode的默认值,一次写入产生的分区数超过限制,任务会直接报错。我遇到过插入一个月数据但分区键有脏数据的情况,动态分区数猛增,最后一个多小时的任务直接失败。处理办法是写入前先对数据做分区键的清洗和去重,同时把上限值调到500到1000这种合理范围,而不是盲目调到几万。
2.2 分桶表的适用场景
分桶(Bucket)在很多人看来是个高级概念,但实际项目里用得其实不多,因为它的好处必须在特定场景下才能体现。分桶是把某个字段的值取哈希,然后按照桶个数分布到固定数量的文件中。好处有三个:一是桶内数据相对有序,配合SMB Join(Sort Merge Bucket Join)可以让两个表在Map端完成Join,不用Shuffle;二是采样查询可以直接基于某个桶进行,速度快很多;三是文件数量可控,不会出现小文件泛滥。
但分桶表有一个很大的限制:使用场景比较窄。除非你的表经常用来做等值Join,而且Join的另一张表也是相同字段、相同桶数的分桶表,否则分桶的优势发挥不出来。而且分桶表的写入逻辑比普通表复杂,如果插入时相关配置没打开,很容易出现桶数和文件结构不一致的问题,反过来影响查询。
我的建议是:不要为了用分桶而用分桶。如果你的业务查询模式是以一个大表和多个维表做Join为主,并且维表本身不大,Map Join已经能解决大部分问题;只有当关联的另一张表也很大(比如超过几GB),无法被Map Join加载时,才值得考虑两张大表都按Join字段分桶,用Bucket Map Join来优化。这属于相对深度的优化,适合数据规模确实很大、查询模式长期稳定的场景。
2.3 存储格式与压缩选型
存储格式对查询性能的影响经常被低估。Hive支持TextFile、SequenceFile、RCFile、ORC、Parquet等格式,其中ORC和Parquet都是列式存储。列式存储的最大优势是:查询只需要读取涉及的列对应的数据块,而不是像行存那样把整行数据都扫一遍。宽表(几十甚至上百列)里只查几个列的场景,列存IO量可能只有行存的十分之一甚至更少。此外列式存储天然适合压缩,压缩比更高,扫描更快。
ORC和Parquet怎么选,要看使用环境。如果集群生态偏Hive和Hadoop的配套组件,ORC通常表现更好,因为它自带轻量索引(Row Group级别的统计信息),支持谓词下推时跳过大量不必要的数据块,而且ORC对Hive的向量化执行支持非常成熟。Parquet在跨生态场景(比如还需要被其他计算组件直接读取)中更通用。我的实践是:纯Hive数仓内部表用ORC,需要与其他组件共享数据或对兼容性要求高的场景用Parquet。
压缩方面,基本原则是:存储和查询用高压缩比格式,中间Shuffle数据用低CPU开销格式。常见组合是:表数据用ORC加ZSTD或Snappy,MapReduce或Tez的中间结果用Snappy或LZ4。ZSTD在压缩率和解压速度上比较均衡,新版本Hive支持后我优先推荐它。Snappy的特点是压缩速度极快,但压缩率一般。GZIP压缩率高但CPU开销大,适合冷数据存储,不适合查询频繁的表。注意一个问题:压缩格式一旦选错,比如把整个表都存成GZIP压缩的TextFile,查询时解压CPU消耗会非常可怕,这种表不管怎么调参数都会慢。
3. SQL写法与数据倾斜的实战优化
3.1 Join优化:MapJoin、Bucket Map Join与Skew Join
Join是Hive中最容易出现性能问题的操作。标准Join也就是Reduce Join,会把两张表的数据都Shuffle到Reduce端,如果某张表数据量很大,Shuffle和排序的代价非常高。优化方向有三个层次。
第一层是MapJoin,也是最常用、效果最明显的优化。MapJoin把小表加载到Map任务的内存中,在Map端直接完成匹配,完全不产生Shuffle。Hive的自动转换由hive.auto.convert.join控制(默认开启),触发条件是表的大小不超过hive.mapjoin.smalltable.filesize。但这个默认阈值在不同版本里不完全一样,而且它判断的是文件大小不是行数,压缩过的表文件很小但解压后可能很大,导致内存溢出。我遇到过一张压缩后不到30MB的表,解压后其实有几个GB,触发MapJoin后Map容器全部OOM。解决方法是先统计确认解压后大小,再决定是否手动强制MapJoin,或者单独提高该Map任务的内存上限。
第二层是Bucket MapJoin。当两张表数据量都很大,无法用MapJoin时,如果两张表都按Join字段分桶,且桶数成倍数关系,可以只在对应桶之间Join,避免全量Shuffle。这需要开启hive.optimize.bucketmapjoin(默认关闭),同时表要用CLUSTERED BY分桶。第三层是Skew Join,通过hive.optimize.skewjoin开启,原理是把倾斜的Join键单独拆分出来处理,比如把某个高频键的数据独立成一个Job去Join,其他键走正常的Join。
日常使用时我的建议是:先用EXPLAIN确认当前Join是MapJoin还是ReduceJoin。如果确认是ReduceJoin且小表不大,可以手动调整hive.auto.convert.join.noconditionaltask.size,把它设为一个计算过的安全值,同时估算解压后的大小,避免OOM。如果小表本身超过几百MB,MapJoin会让任务Map容器内存飙升,反而得不偿失,不如让ReduceJoin正常跑,重点优化执行顺序和倾斜问题。
3.2 数据倾斜的排查与处理
数据倾斜是Hive慢查询里最典型的疑难杂症。表现形式很统一:整个任务卡在99%,或者某一个Reduce任务跑了特别久,其他Reduce早就结束了,集群资源明明有大量空闲,任务就是跑不完。根因是分组或Join的某个Key值数据量远超其他Key值,导致某个Reduce桶承接了大量数据。
最常见的倾斜来源有三个。第一个是GROUP BY维度基数太低,比如按某个只有两三个取值的字段聚合,绝大多数数据挤在一个分组里。第二个是Join的关联键大量相同,比如按用户ID关联,但很多数据行的用户ID为空或同一个默认值,空值或默认值全部落到同一个Reduce。第三个是COUNT DISTINCT,如果WHERE过滤后剩下的重复数据特别多,去重操作会集中在少数Reduce。
处理手段要分场景。GROUP BY倾斜可以打开hive.map.aggr,先做Map端聚合,减少进入Reduce的数据量;如果还不行,可以用加盐(Salting)方式,给倾斜的Key值随机加上前缀,打散成多个Reduce,聚合完再去掉。Join倾斜可以先做空值处理,把空值的关联改成随机字符串,这样在Reduce上就均匀分散了;也可以把主表里的大户单独拎出来,和维表做MapJoin,其余的正常ReduceJoin,最后合并结果。COUNT DISTINCT倾斜的处理建议是尽量改写成GROUP BY嵌套子查询的形式,虽然SQL变长了,但能并行去重,性能往往翻倍。
需要特别提醒的是,加盐方案虽然在很多文章里被当成万能药,但它有个副作用:如果数据维度本身有业务含义,加盐处理后会导致聚合结果需要二次合并,写起来容易出错。我见过有人对订单表加盐后做月度聚合,结果数据被切得七零八落,统计结果直接翻倍。这种错误很隐蔽,调优之前先在测试环境用少量数据验证结果一致性,再上生产。
3.3 常用SQL改写技巧
除了Join和倾斜,很多慢查询纯粹是SQL写法不好。举几个我高频遇到的例子。
第一个是COUNT(DISTINCT)的问题。COUNT(DISTINCT col)在Hive里默认用一个Reduce去重,数据量一大就非常慢。比如统计某天独立用户数,直接写COUNT(DISTINCT user_id)的话,你会看到所有数据都涌向一个Reduce。改成先按user_id GROUP BY一次、再在外层COUNT(*)聚合,虽然多了一层,但去重操作可以分散到多个Reduce并行执行,实际快很多。另一个更彻底的办法是用近似算法,比如approx_count_distinct,在允许一定误差的场景下(比如概览指标、趋势分析)能快好几个数量级。
第二个是IN和EXISTS子查询的改写。Hive对IN和NOT IN子查询的支持一直不够好,老版本甚至直接报错提醒用LEFT SEMI JOIN。即使新版本支持了,执行效率通常也不如JOIN加DISTINCT。我的习惯是:IN语法一律改写成LEFT SEMI JOIN;NOT IN要小心NULL值问题,改成LEFT JOIN加右表字段为NULL的判断更稳妥。
第三个是窗口函数的滥用。窗口函数功能强大,但很多人没意识到每个PARTITION BY都会产生一次排序。如果一条SQL里写了多个窗口函数且PARTITION BY各不相同,执行计划就会出现多次排序和Shuffle。优化思路是尽量合并PARTITION BY相同的窗口函数,或者把不同窗口的计算拆成多个简单查询,让每一层逻辑更清晰。我调过一个连续计算环比、同比、滚动均值的SQL,合并相同窗口后时间从40分钟降到8分钟,效果非常直接。
4. 执行引擎与关键参数的硬核调优
4.1 引擎选型与核心参数
Hive从底层支持多种执行引擎,用hive.execution.engine参数切换,可选MapReduce、Tez和Spark。MapReduce作为默认引擎的历史原因主要是稳定、生态成熟,但它有个天然劣势:每个Job都要写HDFS中间结果,多个Stage串行下来IO开销巨大。Tez把多个Stage构建成一张DAG,中间结果尽量走内存或本地磁盘,避免重复读写HDFS。Spark引擎更进一步,利用内存计算和更灵活的调度,适合复杂多Stage的SQL。
从我实际使用的情况看,如果集群没有特殊的兼容性要求,把引擎切到Tez是性价比最高的一件事,往往不用改别的参数就能看到明显提升。Tez下有几个参数值得关注。hive.tez.container.size控制Tez任务容器内存,默认值可能偏小,需要根据任务数据量调整;hive.tez.java.opts对应JVM堆大小,一般设为容器内存的75%到80%比较安全;tez.task.resource.memory.mb和tez.task.resource.cpu.vcores控制单任务的资源分配。这些参数设置错了会直接导致OOM,设置太大又会让单节点同时运行的任务数变少、降低并行度,需要结合集群规格来测算。
MapReduce引擎虽然慢,但有些场景还是绕不开,比如老版本Hive的某些UDF或者业务里自定义的MR类任务。如果必须用MR,核心参数是mapreduce.map.memory.mb、mapreduce.reduce.memory.mb以及对应的JVM参数。还有一个容易被忽略的是mapreduce.job.reduces,它直接指定Reduce个数。如果业务上对Reduce数没有明确要求,更好的做法是让Hive自动计算,设置hive.exec.reducers.max和每个Reduce处理的数据量阈值。Hive会自动估算合适的Reduce数,手动硬调Reduce数是我见过最常出问题的做法之一:设少了整体负载不均衡,设多了小文件爆炸。
4.2 小文件问题:调优路上最大的隐形杀手
小文件问题在Hive性能调优里太常见了,但很多人意识不到它的危害。所谓小文件,指的是文件大小远小于HDFS默认块大小(128MB或256MB)的文件。一个小文件对应一个内存对象和一个Map输入分片,几万个小文件意味着NameNode内存压力大、Map任务调度开销大、任务启动慢、磁盘寻道多。
小文件的来源主要是三个:一是上游生产任务写入时reduce数量设置不合理,reduce数量基本等于输出文件数量,1000个reduce写出来的就是1000个文件;二是动态分区写入时每个分区都产生一定数量的文件,分区多了文件就多;三是流式组件产出的文件天然偏小,比如一些实时同步任务。
治理手段分事前和事后。事前:控制Reduce个数和输出文件数的关系,宁可让每个文件稍微大一点,也别为了并行度牺牲文件大小;写入分区表时合理设置每个分区的文件大小上限。事后:对已经存在的小文件,用INSERT OVERWRITE重写数据,配合hive.merge.mapfiles=true(合并Map端输出)和hive.merge.mapredfiles=true(合并Reduce端输出),以及hive.merge.size.per.task(合并后目标文件大小)做一次整理合并。我整理过一张2万多个几百KB小文件的表,合并成200多个100MB的文件之后,同一张表的全量扫描查询时间从25分钟降到4分钟,立竿见影。
另一个相关参数是hive.exec.mode.local.auto。开启后,当数据量很小、任务数很少时,Hive会尝试直接在本地模式执行,避免申请集群资源的开销。对测试和开发环境很友好,但生产环境如果对数据量判断错误,可能导致大量任务在客户端本地跑,把提交任务的节点打挂。我一般只在开发测试环境开这个参数,生产环境保持关闭。
4.3 统计信息与CBO:让优化器替你干活
Hive的CBO(Cost-Based Optimizer)从0.14开始引入,通过估算不同执行计划的代价来选择最优方案。但CBO能算得准的前提是统计信息准确。如果表很长时间没有更新统计信息,CBO就是在瞎猜,生成的执行计划可能比默认计划还差。
统计信息收集分手动和自动。手动用ANALYZE TABLE语句,比如ANALYZE TABLE my_table COMPUTE STATISTICS收集表级统计,ANALYZE TABLE my_table COMPUTE STATISTICS FOR COLUMNS收集列级统计,新版本里也可以用COMPUTE INCREMENTAL STATISTICS做增量更新。自动的话,可以开启hive.stats.autogather,Hive在写入数据时会自动收集统计信息,但默认只是表级,列级统计需要额外配置。对于大表,列级统计的ANALYZE比较耗时,一般建议在数据写入后的低峰期做一次,或者集成在ETL流程的最后一步。
还要注意几个和统计信息相关的参数。hive.cbo.enable要确保是true(新版本默认开启);hive.compute.query.using.stats可以让Hive在做简单查询时直接读元数据里的统计值而不是扫描全表,对超大表的COUNT、MAX、MIN这类聚合很有用;hive.stats.fetch.column.stats则控制是否读取列级统计用于CBO优化。实测下来,一张大表重建统计信息之后,原来半小时的查询可能变成几分钟,因为优化器终于能做出像样的Join顺序和Reduce个数决策了。
需要注意,统计信息的准确性依赖写入流程。如果你用的是INSERT OVERWRITE或者通过其他组件写入数据,统计信息可能没有同步更新,需要在ETL任务里显式添加ANALYZE步骤。我因为几次忘记更新导致执行计划退化之后,把统计收集写进了数据仓库的公共调度模板,算是从机制上解决了这个问题。
5. 常见问题与排查技巧实录
5.1 我踩过的几个坑
调优做得多了,踩坑经验比成功案例更有参考价值。这里列几个我反复遇到的典型问题。
第一个坑是自动MapJoin触发OOM。前面提过判断大小用的是文件大小而非解压后大小,压缩率高的表很容易触发。我后来处理的方法是:关掉自动转换,在SQL里用手动提示控制MapJoin,先把小表单独查一遍确认数据量,再决定是否使用。手动提示虽然麻烦,但可控性高,在压缩率高、解压膨胀明显的场景下很有必要。
第二个坑是动态分区写入把元数据撑爆。某次一个按日期动态分区写入的任务,分区键解析出了问题,一下子产生了几万个分区目录,NameNode内存直接告警。后来我在写入SQL里加了数据质量校验步骤,先数一下分区键去重后的数量,超过阈值就直接中止任务。这个保护逻辑看起来简单,但能避免绝大多数生产事故。
第三个坑是改了参数但没确认生效。Hive的参数有些是会话级的,set命令只对当前会话有效;有些是全局的,配置在集群的配置文件中。我在测试环境用set改了一堆参数跑出很好的结果,结果上线时没有改集群配置,生产任务依然是老样子。现在我养成了一个习惯:正式发布前,在执行计划或者日志里确认关键参数的实际值。
第四个坑是升级版本后的行为变化。Hive版本升级后,不少参数默认值会变,比如自动MapJoin的阈值、动态分区的限制数量、CBO是否默认开启等。有一次升级后某个老任务从半小时膨胀到三小时,排查到最后发现是新版本把某个优化默认关掉了。建议每次升级前把关键参数清单过一遍,对比新旧版本的默认值差异。
5.2 调优效果怎么验证
验证调优效果不能只看单次运行时间,因为集群负载波动会掩盖真实变化。我的做法是建立一套可对比的基准测试:选3到5个代表性查询作基准,记录运行时间、资源使用量(CPU时间、Shuffle量、IO量)和最终结果行数。优化前后在同一时间段(避开集群高峰)各跑一遍,至少跑两次取平均值。结果要对得上,这是最容易被忽略的——调优后如果结果不对,一切优化都是白费。
我在项目里维护过一张调优记录表,包含查询标识、优化前时间、优化后时间、改动内容、结果校验结果、上线日期。这个表看起来简单,但对长期维护特别有用。以后某个查询又变慢了,翻记录能快速定位是哪次参数调整或数据变更引起的。另外一个验证技巧是:如果某个查询优化后显著变快,建议在第二天同一时段再跑一次,确认不是集群恰好空闲导致的假象。
5.3 一份可以直接抄的调优参数清单
我把常用参数整理成速查表,按优先级排列。这张表里的参数是我在多个项目里验证过相对安全有效的初始值,实际使用需要结合自身集群规模微调。
| 参数 | 推荐值 | 作用说明 |
|---|---|---|
| hive.execution.engine | tez | 切换执行引擎,提升多Stage效率 |
| hive.exec.parallel | true | 无依赖Stage并行执行 |
| hive.exec.parallel.thread.number | 8 | 并行执行的Stage线程数 |
| hive.auto.convert.join | true | 自动MapJoin开关 |
| hive.mapjoin.smalltable.filesize | 10485760(按解压后大小调整) | 小表阈值 |
| hive.auto.convert.join.noconditionaltask.size | 104857600 | 所有MapJoin输入总大小上限 |
| hive.optimize.skewjoin | true | 开启倾斜Join |
| hive.map.aggr | true | Map端聚合,减少Shuffle数据量 |
| hive.vectorized.execution.enabled | true | 向量化执行 |
| hive.vectorized.execution.reduce.enabled | true | Reduce端向量化 |
| hive.cbo.enable | true | 开启CBO |
| hive.exec.dynamic.partition.mode | nonstrict(按需开启) | 允许全动态分区 |
| hive.exec.max.dynamic.partitions.pernode | 500到1000 | 动态分区上限 |
| hive.merge.mapfiles | true | 合并Map端输出 |
| hive.merge.mapredfiles | true | 合并Reduce端输出 |
| hive.merge.size.per.task | 256000000 | 合并后目标文件大小 |
| hive.exec.compress.intermediate | true | 压缩中间结果 |
| hive.intermediate.compression.codec | SnappyCodec | 中间结果压缩格式 |
| hive.fetch.task.conversion | more | 简单查询不提交MapReduce任务 |
对应的SET语句可以直接整理成一段脚本,发布到生产前记得逐条确认生效。
SET hive.execution.engine=tez; SET hive.exec.parallel=true; SET hive.exec.parallel.thread.number=8; SET hive.auto.convert.join=true; SET hive.mapjoin.smalltable.filesize=10485760; SET hive.optimize.skewjoin=true; SET hive.map.aggr=true; SET hive.vectorized.execution.enabled=true; SET hive.vectorized.execution.reduce.enabled=true; SET hive.cbo.enable=true; SET hive.merge.mapfiles=true; SET hive.merge.mapredfiles=true; SET hive.merge.size.per.task=256000000; SET hive.exec.compress.intermediate=true; SET hive.intermediate.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;这里要强调一个原则:参数不是越多越好,也不是越大越好。一次调优只改1到2个参数,跑一次做对比,确认没问题再改下一个。一次性改十几个参数,一旦出问题根本不知道是哪个引起的。我见过不少开发者把网上的调优参数大全直接贴进生产环境,结果集群资源被吃满,其他任务全部排队,最后只能回滚配置。
调优这条路,经验比理论更值钱。我自己踩过不少坑,比如瞎改参数把生产集群搞到资源争抢、手动加盐导致统计结果翻倍、统计信息没更新导致CBO乱来,每一次都花了不少代价才收敛。如果你正遇到Hive任务慢的问题,我的建议是:别急着改参数,先花十分钟看执行计划和数据分布,想清楚瓶颈在哪个层面,再用最小的改动去验证。这一套流程走熟了,会发现大多数慢查询的根因都差不多,处理起来也就是那几个套路。