必须先建唯一约束,否则MERGE、ON DUPLICATE KEY UPDATE、ON CONFLICT等语法均失效或退化为普通INSERT;多字段唯一需注意NULL可重复,业务要求空值唯一时须用COALESCE或CHECK约束。

直接用 MERGE(SQL Server)、INSERT ... ON DUPLICATE KEY UPDATE(MySQL)、INSERT ... ON CONFLICT DO UPDATE(PostgreSQL)——前提是目标表有明确的唯一约束,否则所有语法都会失效或行为不可控。
必须先建唯一约束,否则所有“防重”语法都白搭
没有 UNIQUE INDEX 或 PRIMARY KEY,MERGE 会报错 The MERGE statement attempted to UPDATE or DELETE the same row more than once;ON DUPLICATE KEY UPDATE 和 ON CONFLICT 根本不触发,退化为普通 INSERT,照样重复。
- 业务主键不能只靠“逻辑上唯一”,比如
ANr字段必须显式加ALTER TABLE t ADD UNIQUE (ANr) - 多字段组合唯一时,注意
NULL可重复:MySQL 允许多个NULL值共存于唯一索引中,若业务要求“空值也需唯一”,得用COALESCE(col, '##NULL##')计算列或加CHECK约束 - 别在临时表或堆表上试这些语法——没索引就等于没防重能力
SQL Server 存储过程中慎用 MERGE
MERGE 看似一步到位,但实际容易踩坑:它不是“智能去重”,而是严格依赖 ON 条件 + 唯一索引匹配。一旦写错,可能误删或漏更新。
-
ON子句必须和唯一索引字段完全一致,例如索引是UNIQUE (tenant_id, order_no),那ON就得写成ON t.tenant_id = s.tenant_id AND t.order_no = s.order_no - 漏写
WHEN NOT MATCHED BY SOURCE THEN DELETE?旧数据不会自动清理,越积越多 - 源数据别直接嵌套多层
SELECT,先写入#staging临时表,方便预去重、加索引、控制执行计划 -
UPDATE SET col = ISNULL(s.col, t.col)是危险写法:源数据为NULL时会覆盖掉目标表已有值,改用CASE WHEN s.col IS NOT NULL THEN s.col ELSE t.col END
MySQL 存储过程里优先用 INSERT ... ON DUPLICATE KEY UPDATE
MySQL 没有 MERGE,INSERT ... ON DUPLICATE KEY UPDATE 是最接近的替代方案,但它的触发逻辑比表面看起来更窄。
- 只响应
PRIMARY KEY或UNIQUE约束冲突,FOREIGN KEY、NOT NULL、CHECK失败时直接报错,ON DUPLICATE KEY UPDATE不执行 - 批量插入用
VALUES ROW(), ROW(), ...,别循环单条——但总长度不能超max_allowed_packet,否则报错Packets larger than max_allowed_packet are not allowed - 想实现“存在则不变,不存在则插入”,别写
col = VALUES(col);真要覆盖,用updated_at = NOW()这类无副作用字段即可 - 时间戳字段如
updated_at,建议用updated_at = GREATEST(VALUES(updated_at), updated_at),避免旧数据覆盖新数据
PostgreSQL 的 ON CONFLICT 要指定冲突目标
PostgreSQL 的 ON CONFLICT 必须显式指定冲突索引或约束名,不像 MySQL 那样隐式匹配唯一键。
- 写法是
ON CONFLICT (anr) DO UPDATE SET ...或ON CONFLICT ON CONSTRAINT uk_anr DO UPDATE SET ...,漏掉括号或约束名会报错there is no unique or exclusion constraint matching the ON CONFLICT specification - 如果表有多个唯一约束(比如
UNIQUE (anr)和UNIQUE (email)),ON CONFLICT (anr)只对anr冲突生效,email冲突仍会报错 -
DO NOTHING比DO UPDATE更轻量,适合纯防重场景;但要注意它返回的ROW_COUNT()是 0,别误判为失败 - 冲突时想读取原行值(比如做计数累加),用
EXCLUDED.col引用新值,target.col引用旧值,别混淆
真正麻烦的从来不是语法怎么写,而是业务主键定义是否经得起并发考验——比如两个系统同时用相同 ANr 插入,但一个带前缀一个不带,或者大小写不统一,再好的 SQL 也拦不住。

















