MERGE是首选但需严格防护,ON条件须覆盖业务唯一键、源数据必须去重、字段映射要显式对齐、批量操作需分批加事务捕获。

MERGE 是首选,但直接扔进存储过程里不加防护,等于埋雷。 它能在一个语句里完成插入、更新、删除,但 ON 条件错、源数据重复、字段映射乱、事务失控,任一环节出问题都会导致数据错漏或执行失败。
ON 条件必须覆盖全部业务唯一键,不能只写主键
很多人把 MERGE 当成 INSERT ... ON DUPLICATE KEY,只在 ON 里写 t.id = s.id。如果业务上真正判重的是 email + tenant_id,而 id 是自增或为空,那这条 MERGE 就会漏更新、重复插入,甚至报错。
- 正确写法:
ON t.email = s.email AND t.tenant_id = s.tenant_id - 禁止在
ON中用函数,如UPPER(t.email)——目标表索引无法走,执行计划可能退化为全表扫描 - 如果
USING是视图或 CTE,确保其输出字段在ON中引用的列已明确定义且非计算列
源数据必须去重,否则 MERGE 直接报错
只要 USING 子句返回两条 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 email, tenant_id ORDER BY updated_at DESC)去重 - 推荐封装成 CTE:
WITH CleanSource AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY email, tenant_id ORDER BY updated_at DESC) rn FROM source),然后WHERE rn = 1 - 别依赖应用层“保证不重复”——存储过程要自己兜底
UPDATE 和 INSERT 字段必须显式对齐,NULL 处理不能偷懒
WHEN MATCHED THEN UPDATE SET 和 WHEN NOT MATCHED THEN INSERT VALUES() 共享同一份源数据,但字段映射、默认值、NULL 语义极易错位。
-
INSERT的VALUES()列表顺序必须和INSERT (col1, col2)完全一致 - 漏掉有
DEFAULT的列且没写DEFAULT关键字,就报错 -
UPDATE分支中,若源字段为NULL且业务要求保留目标原值,不能写col = s.col,得用col = ISNULL(s.col, t.col) - 时间戳字段(如
updated_at)建议在UPDATE分支里显式赋值GETDATE(),MERGE默认绕过AFTER触发器
批量同步必须分批 + 显式事务 + 错误捕获
一次性对上百张表跑 MERGE,不是日志爆满就是锁升级成表级死锁。动态拼 SQL 不难,难的是稳住执行环境。
- 每批最多处理 20–50 行(不是 20–50 张表),用
OFFSET / FETCH或游标分页;@BatchSize建议从 20 开始压测 - 每个
MERGE必须包在BEGIN TRY / BEGIN CATCH里,捕获后记录失败表名和ERROR_MESSAGE() - 跨库同步时,链接服务器权限、RPC 配置、
MAXDOP控制都得提前验证,否则失败时连错误源头都定位不到
真正麻烦的从来不是语法写对了没,而是 ON 条件是否真覆盖了业务唯一性、源数据是否真干净、时间戳是否被忽略、事务边界是否卡在合理粒度——这些点稍一松懈,同步就变成“看起来跑完了,其实数据早歪了”。

















