Hive 中 UNION 与 UNION ALL:不仅仅是去重的区别
在 Hive 的日常开发与面试中,UNION 和 UNION ALL 的区别是一个高频考点。对于有经验的开发者来说,回答这个问题不能仅仅停留在"一个去重,一个不去重"的表面,我们需要深入到执行计划、底层原理以及数据库设计哲学的层面。
核心区别:语义与性能的权衡
简单来说,两者的区别可以归纳为以下几点:
- UNION (去重合并):它代表了数学上的"集合"概念。它会将两个结果集合并,并自动去除重复的行,保证最终结果的唯一性。为了实现这一点,Hive 必须在底层进行额外的排序(Sort)和去重(Deduplicate)操作。
- UNION ALL (简单拼接):它代表了"列表"或"多重集合"的概念。它只是简单地将两个结果集"堆叠"在一起,不做任何去重处理,保留所有的行,包括重复项。因此,它的执行效率极高。
为什么这样设计:显式优于隐式
Hive(以及 SQL 标准)之所以这样设计,核心原因在于性能与语义的解耦。
UNION 的去重代价是昂贵的
在分布式计算引擎(如 MapReduce 或 Tez)中,UNION 的去重操作并不是一个轻量级的任务。它通常需要引入额外的 Shuffle 阶段,将数据按照哈希值重新分区,传输到同一个 Reducer 中,然后进行全局排序和分组,最终才能识别并删除重复行。这个过程伴随着巨大的网络 IO、磁盘 IO 和 CPU 开销。如果数据量达到 TB 级别,一次 UNION 操作可能比 UNION ALL 慢数倍甚至数十倍。
UNION ALL 是纯粹的数据搬运
相比之下,UNION ALL 只是流式地将两个数据流合并输出,不需要全局排序,也不需要 Reduce 阶段参与计算,因此速度极快。
设计哲学:不为不需要的功能买单
如果数据库只提供 UNION(默认去重),那么每次合并数据时,我们都不得不承受去重带来的性能惩罚,即使我们的业务逻辑明确知道数据不会有重复。这显然是一种资源浪费。
反之,如果只提供 UNION ALL,当我们确实需要去重时,就不得不自己手动写复杂的逻辑来实现,增加了开发难度。
因此,这种双接口的设计是一种完美的权衡:
- 当你需要集合的语义(无重复)时,使用 UNION,并承担相应的性能成本。
- 当你只需要合并数据(允许重复或已知无重复)时,使用 UNION ALL,享受极致的性能。
这是一种典型的"显式优于隐式"的设计哲学:让开发者根据业务场景,显式地选择是否需要去重,从而精确控制性能与结果的平衡。
面试中的最佳实践
在实际开发和面试回答中,关于两者的使用,我遵循以下原则:
默认首选 UNION ALL
在 90% 以上的合并场景中,我都优先使用 UNION ALL。原因如下:
- 性能敏感:大数据量下,避免不必要的 Shuffle 是第一要务。
- 数据已知:在良好的数仓设计中,很多合并场景的数据源本身就是互斥的(例如不同业务线、不同日期分区的数据),物理上就不存在重复,无需逻辑去重。
- 下游处理:如果确实需要去重,且数据量可控,我更倾向于在 UNION ALL 之后,通过 GROUP BY 或 DISTINCT 在外层显式控制去重逻辑,这样比隐式的 UNION 更加灵活和可预测。
仅在必要时使用 UNION
只有在业务逻辑严格要求结果集唯一,且无法通过上游逻辑保证数据不重复时,我才会选择 UNION。这通常出现在一些即席查询(Ad-hoc)或数据质量不可控的场景中。
监控执行计划
在编写涉及大量数据的 SQL 时,我习惯通过 EXPLAIN 命令查看执行计划。如果误用了 UNION,执行计划中通常会出现额外的 GroupByOperator 或 Deduplicate 算子,以及伴随的 Shuffle 阶段。一旦发现这些不必要的算子,我会立即将其替换为 UNION ALL。
总而言之,理解 UNION 和 UNION ALL 的区别,本质上是理解 SQL 引擎如何在"计算成本"和"数据语义"之间做权衡。作为有经验的开发者,我的默认选择是性能优先(UNION ALL),仅在业务强需求时才牺牲性能换取语义正确性(UNION)。