首页/数据/数仓建模速查/Upsert 与 MERGE:主键最新态写入
分层与规范64 条

Upsert 与 MERGE:主键最新态写入

需要保留主键最新状态时用 MERGE:匹配则更新、不匹配则插入;Hive ACID 与 MPP 引擎语法接近但限制不同,注意分区条件与并发。

数仓建模速查数据更新于 2026-10-13

MERGE 与 Upsert 的定位

维表与状态表要的不是流水,而是主键的最新态:同一个客户、同一个门店只保留一行。MERGE 用一条语句同时表达「匹配则更新、不匹配则插入」,比先删后插更安全,也更容易写成可重复执行的任务。不同引擎的语法接近,但事务能力、分桶要求和并发限制差别很大。

基本信息

适用场景维表拉链、状态表最新态、幂等重跑
核心语法MERGE INTO 目标 USING 源 ON 条件 WHEN MATCHED / NOT MATCHED
支撑引擎Hive ACID、Doris Unique Key、StarRocks 主键模型、Iceberg、Delta
前置条件目标表需支持行级更新(事务表或主键模型)
风险点全表扫描成本、并发写同分区

典型使用场景

  • 维表更新:客户、门店、商品属性变化后只保留最新版本
  • CDC 落仓:上游变更日志按主键合并到目标表
  • 幂等重跑:任务失败重跑时结果与首次执行一致,不必先清空目标表
  • 状态回填:订单状态、物流状态按最新事件覆盖
  • 分区内更新:限定 dt 分区先过滤,再按主键合并,控制扫描范围

MERGE 写法示例

MERGE 写法示例
-- 目标表按主键合并,仅当前批次数据参与匹配
MERGE INTO dim_customer AS t
USING (SELECT * FROM stg_customer WHERE etl_batch = '20261012_01') AS s
   ON t.customer_id = s.customer_id
WHEN MATCHED AND s.update_time > t.update_time THEN UPDATE SET
     t.customer_name = s.customer_name,
     t.update_time   = s.update_time       -- 用严格大于避免同批次反复更新
WHEN NOT MATCHED THEN INSERT (customer_id, customer_name, update_time)
VALUES (s.customer_id, s.customer_name, s.update_time);
-- 执行前先确认目标表类型与分桶/主键配置

各引擎实现对照

引擎或表类型实现方式注意事项
Hive ACID 事务表MERGE / UPDATE / DELETE需 ORC + 分桶 + transactional 属性
Doris 主键模型导入时按主键覆盖需 Unique Key 模型与合适的 bucket
StarRocks 主键模型导入按主键更新主键索引带来写入开销
IcebergMERGE INTO(v2 行级更新)小文件需定期合并
传统 Hive 外部表不支持只能重写整个分区
MERGE 的匹配条件决定幂等性:用 update_time 严格大于可以避免同一批次重复更新,用大于等于会让每次重跑都产生一次无效写入,累积出大量历史版本。
MERGE 在大分区表上会扫描整个匹配范围,成本远高于普通写入。能按 dt 先过滤就先过滤,把 MERGE 的范围压在当天分区内。

易错点

  • 匹配条件写成 update_time >= 导致同批次数据反复更新,版本与合并文件持续膨胀
  • 在普通 Hive 表上执行 MERGE,报错或行为不确定;应改用事务表或整分区重写
  • 两个任务并发对同一主键集合做 MERGE,结果依赖执行顺序,无法复现
  • 目标表未按主键分桶,MERGE 退化为全表重写,耗时成倍增长

数据来源与参考

  • Apache Hive 官方文档:Merge
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌