能做,MERGE可实现高并发增量同步,但需确保ON条件含全部判重字段、源数据严格去重、目标表有唯一索引、UPDATE/INSERT字段映射一致、OUTPUT正确绑定,否则易引发冲突或错误。

MERGE 本身不支持“高位更新”这个概念——SQL Server 没有叫“高位更新”的语法或机制。你真正想做的,大概率是用 MERGE 实现高并发、高一致性、低冲突的增量同步(比如订单状态、用户配置、指标快照),也就是业内常说的“高位写入场景下的安全更新”。
直接说结论:能做,但必须绕开默认行为里的坑,否则比手写 UPDATE+INSERT 更容易挂。
为什么 ON 条件漏字段会导致重复插入
MERGE 的匹配逻辑完全依赖 ON 子句,它不是“先查再判”,而是用该条件一次性划分 MATCHED / NOT MATCHED 流。
常见错误是只按主键匹配,但业务唯一性实际靠复合键:
比如用户表以 user_id 为主键,但同步依据是 tenant_id + email 组合唯一。若 ON t.email = s.email 却没带上 tenant_id,同一邮箱在不同租户下会被反复插入,触发唯一索引冲突。
-
ON必须包含所有业务判重字段,哪怕目标表主键是单列 - 避免在
ON里用函数(如ON UPPER(t.email) = UPPER(s.email)),会跳过索引,且优化器可能误估行数 - 目标表上对应字段要有唯一索引(非必须主键),否则
MERGE运行时报错:The MERGE statement attempted to UPDATE or DELETE the same row more than once
源数据含重复键时,MERGE 直接报错
MERGE 要求源数据对匹配键严格去重,否则内部执行时无法确定某一行该更新哪条目标记录。
错误现象:The MERGE statement attempted to UPDATE or DELETE the same row more than once —— 这不是数据问题,是语句结构被拒绝。
- 必须提前清洗源数据,例如用
ROW_NUMBER() OVER (PARTITION BY key_col ORDER BY updated_at DESC)取最新一条 - 不能依赖
GROUP BY后直接进USING,SQL Server 对聚合派生表限制严;得套一层SELECT * FROM ( ... ) AS x - 表变量
@staging或临时表#staging是最稳妥的源载体,普通变量或标量值不合法
UPDATE 和 INSERT 字段映射不一致的静默陷阱
WHEN MATCHED THEN UPDATE SET 和 WHEN NOT MATCHED THEN INSERT (...) VALUES (...) 共享同一个源数据集,但字段顺序、NULL 处理、默认值逻辑各自独立。
典型翻车点:目标表 updated_at 允许 NULL,源字段是空字符串或 GETDATE() 表达式,没显式处理就直接 SET updated_at = s.updated_at,结果把 NULL 写进去,覆盖了原值。
-
UPDATE分支里,用ISNULL(s.col, t.col)保留原值;别依赖触发器,MERGE不触发AFTER触发器 -
INSERT的VALUES列表必须跟前面INSERT (col1, col2)严格对齐,漏掉带DEFAULT的列会报错 - 时间戳类字段建议统一在
UPDATE和INSERT中显式设为GETDATE(),而不是靠列默认值
OUTPUT 子句写法不对,就拿不到操作反馈
OUTPUT 是调试和审计的关键,但它不是摆设——写错就等于没写。
错误写法:OUTPUT inserted.* 单独出现;正确写法必须绑定输出目标:
- 要返回客户端:
OUTPUT $action, inserted.*, deleted.*(注意是$action,不是ACTION) - 要存进表变量:
OUTPUT $action, inserted.id INTO @log,且@log结构必须匹配输出列 -
deleted.*只在WHEN MATCHED且用了DELETE分支时才有值;INSERT分支里deleted为空
真正难的不是写对语法,而是理解 MERGE 是个“原子决策引擎”:它先全量扫描、分区、再批量执行,不像 UPDATE 那样逐行锁。一旦 ON 条件松动、源数据未去重、索引缺失,性能崩塌和数据错乱就是瞬间的事。

















