优先使用原子Upsert语句(如MySQL的ON DUPLICATE KEY UPDATE)处理大批量重复数据,必须前置唯一约束和staging表,缺一不可;MERGE等操作依赖ON条件匹配与对应唯一索引,否则报错或失效。

直接结论:别在存储过程里用循环或单条 DELETE/INSERT 处理大批量重复数据,优先走 MERGE(SQL Server)、INSERT ... ON DUPLICATE KEY UPDATE(MySQL)、INSERT ... ON CONFLICT(PostgreSQL)这类原子 Upsert 语句,且必须前置唯一约束和 staging 表。
MERGE 的 ON 条件和唯一约束缺一不可
MERGE 不是万能的“自动去重开关”,它依赖 ON 子句匹配 + 目标表上对应字段的唯一约束。常见翻车点:
- 只写了
ON t.key = s.key,但目标表没建UNIQUE INDEX (key)→ 报错The MERGE statement attempted to UPDATE or DELETE the same row more than once - 把业务主键(如
order_no + source_system)当成了数据库主键,但表实际只有自增id主键 →ON匹配失效,MERGE 当成全量 INSERT - 在
WHEN MATCHED THEN UPDATE里写SET col = ISNULL(s.col, t.col)→ 空值会覆盖已有非空值,应改用CASE WHEN s.col IS NOT NULL THEN s.col ELSE t.col END
MySQL 批量 Upsert 要绕开 max_allowed_packet 和约束盲区
INSERT ... ON DUPLICATE KEY UPDATE 是 MySQL 里最接近 MERGE 的方案,但有两个硬限制:
- 它只响应
PRIMARY KEY或UNIQUE约束冲突,普通索引、CHECK、NOT NULL失败时,整个语句直接报错,ON DUPLICATE KEY UPDATE根本不触发 - 批量插入行数太多会撞上
max_allowed_packet,报错Packets larger than max_allowed_packet are not allowed;建议单批控制在 1000 行以内,用VALUES ROW(), ROW(), ...写法,别用循环拼接 - 如果源数据含
updated_at,更新逻辑要加判断:updated_at = GREATEST(VALUES(updated_at), updated_at),否则旧时间戳可能覆盖新数据
PostgreSQL 的 ON CONFLICT WHERE 容易被当成摆设
INSERT ... ON CONFLICT DO UPDATE 支持带 WHERE 条件,但这个条件作用于“是否执行更新”,不是“是否触发冲突检测”。典型误用:
- 写成
ON CONFLICT (email) WHERE status = 'active' DO UPDATE SET ...→ 实际效果是:只要email冲突,不管status是啥,都先进入冲突分支;WHERE只决定更新是否执行,不改变冲突判定逻辑 - 想按部分索引去重(比如只对
status = 'valid'的记录去重),必须先建CREATE UNIQUE INDEX CONCURRENTLY ON t (email) WHERE status = 'valid',否则ON CONFLICT不识别 - 用表变量或 CTE 做源数据时,PostgreSQL 不允许在
ON CONFLICT中引用 CTE 别名,得走临时表或子查询
为什么 staging 表比直接 SELECT FROM 多层子查询更稳
所有数据库在大批量合并时,都建议把源数据先载入临时表(如 #staging 或 staging_temp),而不是在 MERGE / INSERT 里嵌套多层 SELECT:
- 临时表可预建索引、统计信息、甚至提前去重(
SELECT DISTINCT ... INTO #staging),避免每次执行都重算执行计划 - 多层子查询在 SQL Server 容易触发参数嗅探异常,在 MySQL 可能导致
Using temporary; Using filesort,在 PostgreSQL 容易让 planner 低估行数 - 临时表支持分步验证:先查
SELECT COUNT(*) FROM #staging,再查SELECT COUNT(*) FROM #staging s JOIN target t ON ...,确认匹配逻辑无误后再跑 MERGE
真正难的从来不是语法怎么写,而是你有没有在跑 MERGE 前,确认过那几个字段组合在业务上确实该唯一、在数据库上确实建了唯一索引、在 staging 表里确实没混进脏数据——这三步漏掉任何一步,后面花几小时调优执行计划都是白搭。

















