MERGE需严格防护并发与数据异常:ON必须覆盖业务唯一键且禁用函数;源数据须去重;WHEN MATCHED和NOT MATCHED分支必须显式写出;批量操作需分批、事务及错误捕获;同步断点须落盘至控制表。

MERGE 能用,但直接塞进存储过程里不加防护,大概率在并发或数据异常时崩掉。
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)——目标表索引无法走,执行计划可能退化为全表扫描 - 如果源表是视图或 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 (...) AS rn FROM ... WHERE rn = 1) - 别依赖应用层“保证不重复”——存储过程要自己兜底
WHEN MATCHED 和 WHEN NOT MATCHED BY TARGET 必须显式写出
SQL Server 的 MERGE 语法强制要求至少包含这两个分支,少一个就报 Incorrect syntax near the keyword 'MERGE'。
-
WHEN MATCHED THEN UPDATE SET:不能更新ON中涉及的连接列(如t.id),否则报错 -
WHEN NOT MATCHED BY TARGET THEN INSERT:VALUES()列表顺序必须和INSERT (col1, col2)完全一致;漏掉有DEFAULT的列且没写DEFAULT关键字,就报错 - 时间戳字段(如
updated_at)建议在UPDATE分支里显式赋值GETDATE(),MERGE默认绕过AFTER触发器
批量同步必须分批 + 显式事务 + 错误捕获
一次性对上百张表跑 MERGE,不是日志爆满就是锁升级成表级死锁。动态拼 SQL 不难,难的是稳住执行环境。
- 每批最多处理 20–50 行(不是 20–50 张表),用
OFFSET / FETCH或游标分页;@BatchSize建议从 20 开始压测 - 每个
MERGE必须包在BEGIN TRY / BEGIN CATCH里,捕获后记录失败表名和ERROR_MESSAGE() - 如果需要删除目标中多余记录(即“孤儿数据”),必须额外补
WHEN NOT MATCHED BY SOURCE分支,Oracle 不支持该分支,得单独DELETE
最容易被忽略的是:同步断点必须落盘到控制表,不能存在变量里。每次存储过程都是新会话,局部变量无法跨次传递——失败无声,没人知道停在哪了。










