MERGE 更可靠因其在单条语句中原子化完成匹配、更新、插入、删除,避免 UPDATE+INSERT 的竞态问题;需为 ON 条件列建索引,注意跨库语法差异、ON 逻辑唯一性、NULL 处理及执行前匹配验证。

为什么 MERGE 比 UPDATE + INSERT 组合更可靠
因为 MERGE 在单条语句中完成匹配、插入、更新、删除四类操作,避免了手动写 UPDATE 后再 INSERT 时常见的竞态问题:比如两次查询之间源数据变化,导致重复插入或漏更新。数据库引擎对 MERGE 的整个匹配逻辑做原子性处理,尤其在高并发同步场景下,这是最直接的稳定性保障。
实操建议:
- 必须为
MERGE的ON条件列建立索引,否则性能会断崖式下降(尤其是源表大、目标表无主键时) - Oracle 和 SQL Server 支持
WHEN NOT MATCHED BY SOURCE THEN DELETE,但 PostgreSQL 目前不支持该子句(需用DELETE ... USING配合) - MySQL 完全不支持标准
MERGE,得用INSERT ... ON DUPLICATE KEY UPDATE替代,语义不等价(无法删行、不支持多条件匹配)
ON 子句里不能只写主键,要覆盖业务唯一性
MERGE 的行为完全由 ON 子句决定——它不是“找主键”,而是“找逻辑上该算同一行的记录”。比如同步订单明细时,仅靠 order_id 不够,必须加上 product_id 或 line_number,否则一条订单里多个商品会被错误合并成一行。
常见错误现象:
- 目标表出现重复行:ON 条件太宽(如只用日期字段),多条源记录匹配到同一条目标行,触发多次
UPDATE - 本该更新的行被当新行插入:ON 条件太严(如多加了未清洗的
NULL字段),导致匹配失败 - WHERE 子句不能放在
ON外部过滤源表——必须写进USING的子查询里,否则可能引发意外的 NULL 匹配
UPDATE 和 INSERT 的 SET 子句要区分来源
在 WHEN MATCHED THEN UPDATE SET 中,右侧表达式默认从源表取值;但若混用目标表字段(比如做累加),必须显式加别名,否则报错或逻辑错乱。SQL Server 要求所有更新列都带 source. 或 target. 前缀,Oracle 则允许省略但强烈建议写清。
示例(SQL Server):
MERGE target AS t USING (SELECT id, name, score FROM source WHERE status = 'active') AS s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.name = s.name, t.score = t.score + s.score -- 注意 t.score 是目标值 WHEN NOT MATCHED THEN INSERT (id, name, score) VALUES (s.id, s.name, s.score);
关键点:
- 不要在
INSERT的VALUES里引用目标表别名(t.xxx),语法非法 - 如果源字段可能为
NULL,且业务要求保留目标原值,得用COALESCE(s.name, t.name),而不是直接写s.name - SQL Server 中,
UPDATE SET不能包含聚合函数或子查询,Oracle 允许但性能极差
执行前务必验证匹配结果,别信“应该只影响 N 行”
MERGE 一旦出错,常是大面积误更新或误删除,且不可回滚(除非整个事务回滚)。最稳妥的做法是把 USING 和 ON 部分单独抽出来查一遍匹配关系:
先运行:
SELECT 'MATCHED' AS op, s.*, t.* FROM source s INNER JOIN target t ON s.id = t.id AND s.version > t.version;
再补查漏网之鱼:
SELECT 'NOT MATCHED' AS op, s.* FROM source s LEFT JOIN target t ON s.id = t.id WHERE t.id IS NULL;
容易被忽略的地方:
- 源表和目标表的字符集或排序规则(collation)不一致时,
ON匹配可能静默失败(比如大小写敏感 vs 不敏感) - 某些数据库(如老版本 SQL Server)对
MERGE的统计信息不敏感,即使加了索引,执行计划仍可能走全表扫描 - 日志量巨大——一次同步 10 万行,
MERGE产生的事务日志远超同等数量的独立UPDATE/INSERT

















