在 Hive 中实现MERGE(即 UPSERT:存在则更新,不存在则插入)主要有以下几种方式,具体选择取决于你的Hive 版本和表类型。
1. 原生 MERGE 语句(推荐,Hive 2.2+ / 3.x+)
从 Hive 2.2 开始引入了标准的 SQLMERGE INTO语法,但前提是目标表必须是 ACID 事务表。
前置条件
- Hive 版本 >= 2.2
- 目标表必须是ORC 格式+分桶表+事务表
- 需开启相关配置
-- 开启ACID事务支持SEThive.support.concurrency=true;SEThive.enforce.bucketing=true;SEThive.exec.dynamic.partition.mode=nonstrict;SEThive.txn.manager=org.apache.hadoop.hive.ql.lockmgr.DbTxnManager;建表示例
CREATETABLEtarget_table(idINT,name STRING,amountDECIMAL(10,2))CLUSTEREDBY(id)INTO4BUCKETS STOREDASORC TBLPROPERTIES('transactional'='true');MERGE 语法
MERGEINTOtarget_table tUSINGsource_table sONt.id=s.idWHENMATCHEDANDs.is_deleted=trueTHENDELETEWHENMATCHEDTHENUPDATESETt.name=s.name,t.amount=s.amountWHENNOTMATCHEDTHENINSERTVALUES(s.id,s.name,s.amount);⚠️注意:ACID 表的
MERGE底层会产生 delta 文件,需要定期执行ALTER TABLE ... COMPACT进行合并压缩,否则查询性能会严重下降。
2. INSERT OVERWRITE 模拟 MERGE(最通用,适用于非事务表)
对于普通外部表/内部表(Parquet、TextFile 等),无法使用原生 MERGE,通常用INSERT OVERWRITE+FULL OUTER JOIN或LEFT JOIN来模拟。这是生产环境中最常见的做法。
核心思路
将源表和目标表做关联,通过COALESCE/CASE WHEN决定最终取值,然后覆盖写回目标表(或新分区)。
INSERTOVERWRITETABLEtarget_tablePARTITION(dt='2026-06-30')SELECTCOALESCE(s.id,t.id)ASid,-- 匹配到则取源表值,未匹配到保留目标表原值CASEWHENs.idISNOTNULLTHENs.nameELSEt.nameENDASname,CASEWHENs.idISNOTNULLTHENs.amountELSEt.amountENDASamountFROMtarget_table tFULLOUTERJOINsource_table sONt.id=s.idWHEREt.dt='2026-06-30';-- 只处理目标分区变体:仅处理增量数据(性能优化)
如果源表只是增量数据,可以先 UNION 再 GROUP BY 去重:
INSERTOVERWRITETABLEtarget_tablePARTITION(dt='2026-06-30')SELECTid,name,amountFROM(SELECTid,name,amount,ROW_NUMBER()OVER(PARTITIONBYidORDERBYupdate_timeDESC)ASrnFROM(SELECT*FROMtarget_tableWHEREdt='2026-06-30'UNIONALLSELECT*FROMsource_table)combined)rankedWHERErn=1;💡优点:不依赖 ACID,兼容所有存储格式和 Hive 版本。 💡缺点:每次都是全量重写整个分区,数据量大时 I/O 开销高。
3. 各方案对比总结
| 维度 | 原生 MERGE (ACID) | INSERT OVERWRITE 模拟 |
|---|---|---|
| Hive 版本要求 | ≥ 2.2 | 任意版本 |
| 表格式要求 | 必须 ORC + 分桶 + 事务属性 | 任意格式(Parquet/ORC/CSV等) |
| 写入方式 | 增量写入 delta 文件 | 全量覆盖分区 |
| DELETE 支持 | ✅ 原生支持 | ❌ 需额外过滤逻辑 |
| 查询性能 | 需 Compact 后才好 | 写完即可查,无额外维护 |
| 并发安全 | ✅ 有锁机制 | ❌ 无锁,需调度层保证 |
| 适用场景 | 实时/准实时 CDC 入仓 | 离线 T+1 批量 ETL |
| 运维复杂度 | 高(需管理 Compaction) | 低 |
💡 最佳实践建议
- 离线数仓首选方案 2:大多数离线 ETL 场景不需要 ACID 特性,
INSERT OVERWRITE更稳定、更易排查问题,且与 Spark/Flink 生态兼容性更好。 - CDC / 实时同步选方案 1:如果需要频繁小批量 UPSERT 且对延迟敏感,使用 ACID 表的
MERGE,但务必配置自动 Compaction 或定时手动触发。 - 考虑替代引擎:如果 MERGE 是高频操作,建议评估Apache Iceberg / Hudi / Delta Lake等数据湖格式,它们原生支持高效 Upsert/Merge,且无需 Hive ACID 的 Compaction 负担,已成为现代数据架构的主流选择。
- 避免大表全量 OVERWRITE:如果目标表非常大且只有少量变更,可考虑按主键范围拆分为多个子任务并行处理,或使用分桶表配合
INSERT INTO追加 + 读取时去重的策略。