Hive SQL 慢起来真是要命。跑一分钟还能忍,一跑十几分钟甚至更久,业务方过来问「你的数据怎么还没出来」,那种感觉,懂的都懂。

最近就遇到这么个真实案例——一个 Hive SQL 脚本,短的时候一分多钟,长的时候能跑到十几分钟,而且极其不稳定。查了一圈,最终只调了两个参数,把 447 秒干到了 39 秒,提升了整整 11 倍。

讲真,这事儿没有多高深,但背后的思路值得聊聊。

别上来就瞎调。第一步永远是看慢在哪里。

Hive 在 Tez 引擎下的执行链路大致是:编译 SQL → 生成物理计划 → 向 YARN 申请资源 → 提交 DAG → 执行 DAG。绝大多数时间都耗在最后一步「Run DAG」上,这是正常的,毕竟计算就在这一步发生。

但有个坑——有时候 YARN 资源紧张,申请 AM 就卡半天。如果不看总体执行日志,你可能会以为是 SQL 本身的问题,实则是在排队等资源。

所以先看日志,确认瓶颈到底在哪一层。

找到瓶颈:MAP1 只开了 8 个 task

我这边的 SQL 主要是一张事实表关联多张维表,union 后写入目标表。通过 explain 拿到执行计划,发现三段子查询分别对应 MAP1、Reducer8 和 Reducer16。

一看运行日志,问题一目了然:

MAP1 耗时 439 秒,但只开了8 个 task。而 Reducer8 和 Reducer16 分别只有 4 秒和 3 秒就跑完了。

打开网易新闻 查看精彩图片

所以瓶颈就是 MAP1,原因很简单——并行度不够。说白了,8 个人干的活,你让 8 个人干,你让一堆人等着,能快才怪。

为什么只有 8 个 task?

查了一下主表的元数据:89 个分区,522 个小文件,总行数 2170 万,总数据量约 4.6 GB。

关键问题是小文件太多,522 个文件平均每个才 8MB 左右。虽然 Hive 的 CombineHiveInputFormat 会自动合并小文件,但 Tez 会重新打包数据切片动态调节 task 数量。默认情况下,它觉得这些文件够小了,不需要开太多 task。

解决方案是用和来控制单个切片的大小,从而间接调节 task 数:

打开网易新闻 查看精彩图片

调完之后,MAP1 的 task 数从8 个增加到 94 个,并行度直接拉满。

打开网易新闻 查看精彩图片

但注意别太贪心。切片太小、task 太多,意味着要申请大量 YARN 容器,不光你的任务自身有开销,还会挤占集群其他人的资源。64MB-128MB 这个区间是我测试下来比较合适的,具体根据你集群的情况来调整。

另一个关键优化:开启向量化

光增加并行度还不够,每个 task 内部的执行效率也要看。

这里就要聊到 Hive 的向量化执行了。传统的行式处理是一条一条地处理数据,CPU 利用率很低;向量化则是批量处理——默认每批 1024 行——能大幅提升 CPU 利用率。

开启方式很简单,加几行配置就行:

打开网易新闻 查看精彩图片

但有个坑要注意:向量化要求表格式是 ORC 或 Parquet 这种列式存储,而且有些函数不支持原生向量化。比如instr函数就是不支持的,它会迫使整个执行链路回退到行模式,导致你开了向量化也等于没开。

解决方案:把 instr 换成 like。比如 instr(col, 'something') > 0 可以等价替换成 col like '%something%'。因为 like 有对应的原生向量化版本,差别就在这里。

打开网易新闻 查看精彩图片

效果:447秒 → 39秒

两套优化打下去,最终效果:

  • 优化前:447 秒
  • 优化后:39 秒
  • 提速:约11 倍

打开网易新闻 查看精彩图片

讲道理,这个优化思路不复杂,但很多同学遇到 Hive SQL 慢的时候第一反应是「是不是数据量太大了?」或者「是不是要上 Spark 了?」——其实不一定。先看看并行度够不够、向量化开没开、有没有函数不支持向量化,这几个点排查下来,大多数场景不用换引擎也能解决问题

几个可以记在小本本上的点

这次优化过程中还有一些额外的收获,一并分享了:

  1. 裁剪—— 别 select *,只取需要的列,减少 IO
  2. 分区裁剪——where 条件尽量用分区字段,不然全表扫描谁也救不了
  3. 尽早过滤——子查询或 CTE 里先把数据过滤掉,减少中间数据量
  4. 了解你的函数——有些看着无害的函数,可能让你的向量化执行白开了

当然,除了上面这些,还有数据倾斜、Reducer 并行度自动调节等方向可以深入。但以上几点是性价比最高的,花最少的时间拿最大的收益。

你手头的 Hive SQL 遇到过这种情况吗?有没有什么奇技淫巧分享一下?评论区聊聊。