SQL Server的MERGE是Upsert最可靠方式,但必须同时包含WHEN MATCHED和WHEN NOT MATCHED分支,ON条件需基于非NULL唯一键,源数据应预处理并加HOLDLOCK锁以保障并发安全。

MERGE 是 SQL Server 存储过程中实现 Upsert 最可靠的方式,但写错一行就报错或行为异常——它不是“语法对就能跑”,而是必须满足匹配逻辑、分支完整性、并发保护三重约束。
WHEN MATCHED 和 WHEN NOT MATCHED 都不能省
SQL Server 强制要求 MERGE 必须同时包含 WHEN MATCHED 和 WHEN NOT MATCHED 分支,漏掉任一个直接报错:Incorrect syntax near the keyword 'MERGE' 或 The MERGE statement attempted to UPDATE or DELETE the same row more than once。
-
WHEN MATCHED THEN UPDATE SET ...只判断ON条件是否成立,不比较字段值是否真有变化;想避免无意义更新,得手动加CASE或WHERE(但注意:SQL Server 不允许在WHEN MATCHED后直接跟AND条件) -
WHEN NOT MATCHED THEN INSERT (col1, col2) VALUES (s.col1, s.col2)必须用VALUES,不能写成SELECT或省略括号 - 如果业务上确实只做插入或只做更新,也要显式写出另一分支并执行
DO NOTHING,比如WHEN MATCHED THEN UPDATE SET @dummy = @dummy(虽不优雅,但合法)
ON 条件必须基于唯一键,且不能含 NULL 比较
ON t.id = s.id 看似简单,但若 id 列允许 NULL,该条件在任一为 NULL 时恒为 UNKNOWN,导致所有行都进 WHEN NOT MATCHED 分支——结果就是重复插入。
- 目标表的匹配列(如
id)必须是主键或带唯一索引,否则可能触发多匹配错误:The MERGE statement attempted to update or delete the same row more than once - 源数据建议走临时表(如
#staging)或表变量(@staging),提前WHERE id IS NOT NULL过滤,别指望 ON 子句兜底 - 避免在 ON 中用函数,比如
UPPER(t.email) = UPPER(s.email),会绕过索引,批量时性能断崖下跌
批量 Upsert 必须用临时表 + HOLDLOCK
逐行调用 MERGE(比如 Python 循环执行)在千行以上就明显变慢;真正高效的做法是把数据先批量载入临时表,再单次 MERGE 处理全集。
- 创建本地临时表:
CREATE TABLE #staging (id INT, name NVARCHAR(50), score INT),并在id上建索引:CREATE INDEX IX_staging_id ON #staging(id) - 用
SqlBulkCopy或bcp批量灌入数据,比executemany快 10–100 倍 - 高并发场景下,必须加
WITH (HOLDLOCK)锁提示:MERGE INTO users WITH (HOLDLOCK) AS t USING #staging AS s ON t.id = s.id ...,否则大概率出现重复插入或丢失更新
PostgreSQL 和 MySQL 用户别硬套 MERGE
PostgreSQL 没有 MERGE,正确姿势是 INSERT ... ON CONFLICT (id) DO UPDATE SET ...;MySQL 对应的是 INSERT ... ON DUPLICATE KEY UPDATE。强行在这些数据库里模拟 MERGE 写法,只会触发语法错误或约束冲突。
- PostgreSQL 的
ON CONFLICT要求id列已定义唯一约束,否则报错:there is no unique or exclusion constraint matching the ON CONFLICT specification - MySQL 的
ON DUPLICATE KEY UPDATE只响应 PRIMARY KEY 或 UNIQUE 约束冲突,普通索引无效;且 INSERT 因其他约束(如NOT NULL)失败时,UPDATE 分支根本不会执行 - Oracle 支持 MERGE,但不支持
WHEN NOT MATCHED BY SOURCE分支,想实现“源中没有则删除目标行”,得拆成 MERGE + 单独 DELETE 两步
真正难的不是写对语法,而是在并发、空值、索引缺失、约束冲突这些边界条件下,让 MERGE 行为可预测——它不像 SELECT 那样“查不到就空”,而是“匹配错就炸”。

















