MERGE需严格防护并发与数据异常:ON条件须覆盖全部业务唯一键且禁用函数,源数据必须前置去重,UPDATE/INSERT字段须显式对齐并妥善处理NULL,批量操作要分批加事务和错误捕获。

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 (...) rn FROM staging WHERE is_valid = 1) MERGE INTO target USING CleanSource ON ... 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)(SQL Server)或NVL(s.col, t.col)(Oracle) - 时间戳字段(如
updated_at)建议在UPDATE分支里显式赋值GETDATE()或SYSDATE;MERGE默认绕过AFTER触发器
批量同步必须分批 + 显式事务 + 错误捕获
一次性对上百张表跑 MERGE,不是日志爆满就是锁升级成表级死锁。
- 每批最多处理 20–50 行(不是 20–50 张表),用
OFFSET / FETCH或游标分页;@BatchSize建议从 20 开始压测 - 每个
MERGE必须包在BEGIN TRY / BEGIN CATCH(SQL Server)或BEGIN ... EXCEPTION(Oracle)里,捕获后记录失败表名和ERROR_MESSAGE() - Oracle 12c 及之前不支持
WHEN NOT MATCHED BY SOURCE,想实现“目标存在但源不存在时做处理”,只能拆成两步:先MERGE,再单独DELETE或UPDATE目标表中不在源数据里的记录
最常被忽略的点是:ON 条件是否真能覆盖所有业务唯一场景,以及源数据是否在进入 MERGE 前已被可靠去重——这两处一松动,整个同步过程就失去原子性保障,变成“看着像原子,实则处处裸奔”。











