MERGE需严格控制并发与数据质量。ON条件须覆盖全部业务唯一键且避免函数,源数据须去重,UPDATE/INSERT字段须显式对齐并处理NULL,时间戳应显式赋值,大批量操作须分批、显式事务及错误捕获。

MERGE 能在一个语句里完成增删改,但直接塞进存储过程里不加控制,很容易在并发、重复数据或索引缺失时崩掉。
ON 条件必须覆盖全部业务唯一键,不能只写主键
很多人把 MERGE 当成“高级 INSERT … ON DUPLICATE KEY”,只在 ON 里写主键字段,结果同步时出现重复插入或漏更新。比如用户表按 email 唯一,但 ON t.id = s.id 就完全失效——id 可能为空或未生成,而 email 才是真实判重依据。
- 务必把所有用于业务去重的列都放进
ON,例如ON t.email = s.email AND t.tenant_id = s.tenant_id - 避免在
ON中用函数(如UPPER(t.email)),否则目标表索引无法走,执行计划可能退化为扫描 - 如果源数据本身含重复键(比如两个
email='a@b.com'的记录),MERGE会直接报错The MERGE statement attempted to update or delete the same row more than once,得先在USING子查询里用ROW_NUMBER() OVER (PARTITION BY ...)去重
UPDATE 和 INSERT 字段映射必须显式对齐,别依赖默认值
MERGE 的 WHEN MATCHED THEN UPDATE SET 和 WHEN NOT MATCHED THEN INSERT ... VALUES() 共享同一个源数据集,但字段类型、NULL 处理、默认逻辑容易错位。
-
INSERT的VALUES()列表必须跟INSERT(…)的列顺序严格一致;漏掉有DEFAULT的列且没写DEFAULT关键字,就会报错 -
UPDATE分支中,如果源字段为NULL且业务要求保留目标原值,不能写col = s.col,得用col = ISNULL(s.col, t.col) - 时间戳字段如
updated_at建议在UPDATE分支里显式设为GETDATE(),别指望触发器——MERGE默认绕过AFTER触发器
批量同步 10000 张表时,必须分批 + 显式事务 + 错误捕获
一次性对上万张表跑 MERGE,不是日志爆满就是锁升级成表级死锁。动态生成语句本身不难,难的是稳住执行环境。
- 用
OFFSET / FETCH或游标分批,每批最多 20–50 张表,避免单事务过大;@BatchSize建议从 20 开始压测 - 每条
MERGE必须包在BEGIN TRY / BEGIN CATCH里,捕获错误后记录失败表名和ERROR_MESSAGE(),不能让一个表失败中断整个流程 - 禁用隐式事务(
SET IMPLICIT_TRANSACTIONS OFF),所有MERGE都用显式BEGIN TRAN / COMMIT / ROLLBACK控制边界
真正麻烦的从来不是语法拼对,而是 ON 条件是否穷尽业务唯一性、源数据是否干净、以及出错时能不能准确定位到哪张表哪一行坏了——这些地方没盯住,MERGE 跑得越快,数据越危险。

















