Featured image of post 用 DuckDB MERGE INTO 干掉 ETL 里的 Upsert 噩梦

用 DuckDB MERGE INTO 干掉 ETL 里的 Upsert 噩梦

一行 SQL 替代 20 行 Python 代码,掌握 DuckDB MERGE INTO 幂等写入技巧,彻底告别 ETL 中的 Upsert 竞态条件和脏数据问题。

用 DuckDB MERGE INTO 干掉 ETL 里的 Upsert 噩梦:一行 SQL 替代 20 行 Python 代码

难度:⭐⭐⭐ | 预计耗时:15 分钟上手,之后告别脏数据

DuckDB MERGE INTO 架构图

一、你的 ETL 是不是也在写这种代码?

很多数据工程师的日常是这样的:

# 传统做法:检查是否存在,然后决定 INSERT 还是 UPDATE
existing = con.execute("SELECT id FROM daily_sales WHERE date = '2026-06-30'").fetchall()
if existing:
    con.execute("UPDATE daily_sales SET ... WHERE date = '2026-06-30'")
else:
    con.execute("INSERT INTO daily_sales VALUES (...)")

这段代码有什么问题?

  1. 并发不安全:两个进程同时检查,发现都不存在,就插了两条重复数据
  2. 性能极差:两次网络往返(SELECT + INSERT/UPDATE)
  3. 代码冗长:每张表都要写一遍 check-then-insert 的逻辑
  4. 事务复杂度:还需要手动管理事务回滚

而 DuckDB 的 MERGE INTO 让你一行 SQL 搞定一切:判断存在则更新,不存在则插入,原子操作,无竞态。

二、幂等写入的核心原理

DuckDB 支持 SQL:2003 标准的 MERGE 语法(也叫 UPSERT),从 v0.9 开始全面支持。它的本质是一个原子化的更新插入操作:

MERGE INTO target_table
USING source_data
ON target_table.key = source_data.key
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT VALUES ...

关键优势:

  • 原子性:整个操作在一个事务内完成,不存在竞态条件
  • 批量操作:一次性处理成千上万条记录,远快于逐条循环
  • 可读性强:意图清晰,匹配则更新,不匹配则插入

三、完整实战:电商销售日报的幂等写入

假设你每天从消息队列消费销售数据,需要写入 DuckDB 的分析表。数据可能包含重复消息,你需要确保同一笔订单不会重复累加。

Step 1:建表

import duckdb

con = duckdb.ATTACH (":memory:")

# 主表:按订单号去重
con.execute("""
---

## 本文信息

| 项目 | 内容 |
|------|------|
| DuckDB 版本 | v1.5.x(部分功能基于 v2.0 Preview) |
| 最后验证 | 2026-09-12 |
| 测试环境 | Linux / x86_64 / 16GB RAM |
| 官方文档 | [DuckDB Documentation](https://duckdb.org/docs/) |
| GitHub | [pengzz9527/duckdb-blog](https://github.com/pengzz9527/duckdb-blog) |

如发现错误,欢迎通过 [GitHub Issue](https://github.com/pengzz9527/duckdb-blog/issues) 或邮件 [email protected] 反馈。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 Stack 由 Jimmy 设计