离线数仓基石:Hive与Spark SQL的深度对比,ETL流程设计与分区策略

聊到离线数仓,Hive 和 Spark SQL 是绕不开的两个核心引擎。很多人觉得它们差不多,都是写 SQL 跑批处理。其实不然。我在一线摸爬滚打这些年,见过太多因为选型失误导致凌晨三点还在调任务的案例。今天咱们就把这两兄弟掰开揉碎,看看它们到底差在哪,以及怎么用好它们。

Hive vs Spark SQL:不只是快慢的问题

先给个结论:Hive 是“稳”,Spark SQL 是“快”。但实际选型远没这么简单。

对比维度 Hive (MapReduce/Tez) Spark SQL
执行模型 MapReduce 多阶段,磁盘落中间结果 DAG 计算,内存管道,尽量不落盘
启动开销 每个 Job 拉起 JVM,分钟级 Executor 常驻,秒级
数据规模 PB 级稳定,不怕 OOM TB~PB 级,内存压力大时需调优
SQL 兼容性 HiveQL,部分函数行为不同 ANSI SQL 更全,Spark 3.x 进步明显
小文件处理 天然合并机制,但慢 需手动 coalesce/repartition
资源弹性 YARN 调度,可动态分配 动态资源分配,但 shuffle 时容易卡住

我的经验之谈:日调度任务,数据量在 500GB 以下,我倾向用 Spark SQL。超过 5TB 的复杂 ETL,尤其涉及多表大 Join,我反而会先用 Hive 跑一遍,再用 Spark 做后处理。别问为什么,问就是被 OOM 坑过。

ETL 流程设计:分层是门艺术

离线数仓的 ETL,说白了就是“搬砖+装修”。但砖怎么搬,装修顺序怎么排,直接决定你后面能不能睡个安稳觉。

我习惯把 ETL 分成四个阶段:

  1. 数据接入(ODS):原封不动拉过来,加个分区字段和业务日期。别做任何清洗,保留现场。
  2. 数据清洗(DWD):去重、空值处理、格式统一。这里最容易踩坑——我曾经因为一个字段的空格没 trim,导致下游关联全错位。
  3. 数据整合(DWS):轻度聚合,按主题域建模。比如用户主题、订单主题。宽表在这里诞生。
  4. 数据应用(ADS):面向报表、BI、接口的最终表。通常很窄,指标明确。

避坑指南:我曾经在 DWD 层直接做全量关联,结果一个 10 亿行的表跑了 6 小时。后来改成增量+拉链表,半小时搞定。记住:能增量就别全量,能分区就别扫全表。

分区策略:选对了,性能翻倍

分区是离线数仓的灵魂。选错了分区键,查询就是全表扫描,跑死你。

我总结了三类常用分区策略:

策略 适用场景 注意事项
时间分区(dt) 日志、订单、行为数据 粒度建议天,避免小时分区导致小文件爆炸
业务分区(region/city) 地域性强的数据 分区数控制在 100 以内,太多影响元数据性能
组合分区(dt+type) 多维度筛选场景 层级别超过 3 层,否则 HDFS 目录树太深

举个例子,我做过一个电商数仓:

-- 推荐的分区设计
CREATE TABLE dwd_order_detail (
  order_id STRING,
  user_id STRING,
  product_id STRING,
  amount DECIMAL(10,2),
  status STRING
)
PARTITIONED BY (dt STRING, region STRING)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');

-- 查询时利用分区裁剪
SELECT * FROM dwd_order_detail
WHERE dt = '2024-01-15'
  AND region = '华东';

注意:分区字段不要用中文,不要用特殊字符。我见过有人用“2024年01月”做分区值,结果 Hive 解析直接报错。老老实实用 yyyy-MM-dd 格式。

Spark SQL 在 ETL 中的实战技巧

用 Spark SQL 做 ETL,有几个坑我替你们踩过了:

  • 小文件问题:Spark 默认输出分区数等于最后 stage 的并行度。我习惯在写入前加 .coalesce(n) 或 .repartition(n),n 根据数据量来,一般 1GB 对应 1 个文件。
  • Shuffle 优化:大表 Join 小表,用 broadcast join。我一般手动设置 spark.sql.autoBroadcastJoinThreshold=104857600(100MB),超过这个值就考虑分桶。
  • 动态分区写入:开启 spark.sql.sources.partitionOverwriteMode=dynamic,避免全表覆盖。这个我吃过亏——一次误操作把历史分区全清了。
-- Spark SQL 动态分区写入示例
INSERT OVERWRITE TABLE dws_user_daily
PARTITION (dt)
SELECT
  user_id,
  SUM(amount) AS total_amount,
  dt
FROM dwd_order_detail
WHERE dt = '2024-01-15'
GROUP BY user_id, dt;

一张图看懂离线数仓核心逻辑

下面这张图是我自己总结的,涵盖了 Hive/Spark SQL 选型、ETL 分层和分区策略的关系。你把它存下来,做架构设计时拿出来对照,基本不会跑偏。

离线数仓核心逻辑:选型 · 分层 · 分区 数据源(业务库/日志) ODS 层(原样接入) DWD 层(清洗去重) DWS 层(轻度聚合) ADS 层(报表/接口) 引擎选型建议 小数据量 → Spark SQL 大数据量 → Hive 混合场景 → 组合使用 分区策略 时间分区(dt) 业务分区(region) 组合分区(dt+type) 层级 ≤ 3 层

总结一下

离线数仓的基石,说白了就三件事:选对引擎、分好层、定好分区。Hive 和 Spark SQL 不是二选一,而是互补。ETL 分层别贪多,四层足够。分区策略提前规划好,别等数据量上来了再改。

嗯,这些经验都是我用加班和报警堆出来的。你照着做,至少能少踩一半的坑。