SQL Server 的 MERGE 语句需基于唯一键在 ON 子句中精确匹配,WHEN MATCHED 仅判断连接条件是否成立而非列值差异,漏写 WHEN NOT MATCHED 或误用条件易致错误或性能下降。

MERGE 语句在 SQL Server 中的典型写法
SQL Server 的 MERGE 是实现 Upsert(存在则更新、不存在则插入)最直接的方式,但它不是“写完就跑”,必须明确指定匹配逻辑和操作分支。常见错误是漏掉 WHEN NOT MATCHED THEN INSERT 或误把 WHEN MATCHED 当成“只要字段值不同就更新”——实际上它只判断 ON 子句的连接条件是否成立,不比较具体列值。
基本结构如下:
MERGE target_table AS t
USING (SELECT @id AS id, @name AS name, @score AS score) AS s ON t.id = s.id
WHEN MATCHED THEN
UPDATE SET name = s.name, score = s.score
WHEN NOT MATCHED THEN
INSERT (id, name, score) VALUES (s.id, s.name, s.score);
-
ON子句必须基于唯一键或主键,否则可能触发多匹配导致报错The MERGE statement attempted to update or delete the same row more than once - 源数据(
USING部分)建议用子查询或表变量封装,避免直接拼接参数引发类型隐式转换问题 - 不能在
WHEN MATCHED分支里加额外条件(如AND t.score s.score)来控制更新时机——虽然语法允许,但会显著降低执行计划稳定性,且 SQL Server 2019+ 对带条件的MATCHED优化有限
PostgreSQL 怎么做 Upsert(没有 MERGE)
PostgreSQL 没有 MERGE,但支持 INSERT ... ON CONFLICT,这是更轻量、更可控的 Upsert 实现方式。它底层依赖唯一约束或主键冲突检测,比 MERGE 更少出错,也更容易调试。
示例:
INSERT INTO users (id, name, score) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, score = EXCLUDED.score;
-
ON CONFLICT (id)中的id必须是已定义的唯一索引或主键列,否则报错there is no unique or exclusion constraint matching the ON CONFLICT specification -
EXCLUDED是个伪表,代表本次被拒绝插入的那行数据,不能写成VALUES或引用外部变量 - 如果想只在某些字段变化时才更新(比如避免无意义的时间戳覆盖),得显式写条件:
DO UPDATE SET updated_at = NOW() WHERE users.score EXCLUDED.score
MySQL 的 INSERT ... ON DUPLICATE KEY UPDATE 注意点
MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE 实现 Upsert,表面简单,但几个细节容易翻车:它只响应 PRIMARY KEY 或 UNIQUE 约束冲突,普通索引无效;而且一旦触发更新,LAST_INSERT_ID() 的行为会变——插入时返回新 ID,更新时返回原 ID,这点在业务层生成关联记录时容易出错。
- 必须确保表上有对应字段的
UNIQUE或PRIMARY KEY,否则语句静默变成纯插入,不会报错也不会更新 - 更新子句中不能直接引用要插入的值,得用
VALUES(col_name)函数,例如:ON DUPLICATE KEY UPDATE name = VALUES(name), score = VALUES(score) - 如果
INSERT语句含多行,且部分行冲突、部分不冲突,MySQL 默认继续执行(非事务中断),但影响行数统计会包含插入+更新总数,需靠ROW_COUNT()判断实际插入了几行
跨数据库写 Upsert 时最该检查的三件事
别指望一套 SQL 能在 SQL Server、PostgreSQL、MySQL 上通用。哪怕只是改个关键字,运行时也可能完全失效或产生意外结果。
- 确认目标列是否有唯一约束:SQL Server 的
MERGE ON、PostgreSQL 的ON CONFLICT、MySQL 的ON DUPLICATE KEY全部依赖这个,缺了就退化成纯插入 - 检查空值(
NULL)处理逻辑:三个平台对NULL = NULL的判断完全不同,尤其当唯一键含可空列时,PostgreSQL 认为NULL不冲突,MySQL 和 SQL Server 可能认为冲突或不冲突,取决于索引类型 - 留意并发安全:所有 Upsert 方案在高并发下都可能因“检查-执行”间隙导致重复插入(即所谓的“thundering herd”),真正保险的做法是在应用层加分布式锁,或在数据库层用
SELECT ... FOR UPDATE配合事务——但这会让 Upsert 失去原子性优势,得权衡

















