MERGE非原子操作,高并发下因“查-插”无锁保护导致重复INSERT;ON字段有唯一索引也无法避免;需用ROW_NUMBER()去重源表、显式WHERE防重启动、或改用INSERT+异常捕获+SELECT FOR UPDATE。

MERGE不是原子操作,高并发下两个事务可能同时判定“目标不存在”,都执行INSERT
ON条件匹配失败导致重复INSERT
MERGE执行分两步:先用ON子句查目标表是否存在匹配行,再决定走UPDATE还是INSERT。但这个“查”和“插”之间没有锁保护。当两个会话几乎同时执行,都看到目标行不存在,就会都进入INSERT分支,最终触发ORA-00001或SQL Server的唯一约束冲突。
- 这不是数据库bug,而是
check-then-act竞态的典型表现 - 即使
ON字段有唯一索引,也无法阻止该问题——索引只在INSERT真正执行时才校验 - Oracle 21c、SQL Server、达梦等均存在此行为,MySQL因不支持标准
MERGE而绕过该问题(但用INSERT ... ON DUPLICATE KEY UPDATE有静默覆盖风险)
WHERE子句缺失放大DML重启动风险
Oracle在WHEN MATCHED THEN UPDATE后若不加WHERE,可能在事务被阻塞又恢复时基于旧快照执行更新,造成逻辑错乱(比如把id=1的记录更新后,另一事务把id改成了10,原UPDATE仍成功作用于这行,结果出现(10, 'Tom')这种本不该存在的组合)。
- 必须显式写
WHERE t1.id = 1,且与ON中的谓词严格一致 - 该
WHERE不是冗余,它把UPDATE约束到“当前仍满足ON条件”的行,防止重启动误操作 - PostgreSQL和SQL Server无此重启动机制,但仍有并发INSERT冲突问题
源表数据重复直接触发ORA-30926而非主键冲突
很多人以为报ORA-30926是目标表问题,其实根源在源表:ON字段(如order_id)在USING子句提供的数据里出现多次,导致一个目标行被多个源行匹配。数据库拒绝执行这种模糊决策。
- NULL值也会触发该错误——多数数据库将多个
NULL视为相等 - ODBC场景下还可能因数值精度传递失真(如
NUMBER(10,2)字段绑定高精度浮点变量),使ON误判为不匹配,进而走入INSERT分支并撞上约束 - 修复必须前置:在
USING里用ROW_NUMBER() OVER (PARTITION BY join_key ORDER BY updated_at DESC)取最新一条,不能依赖目标表索引兜底
真正稳的方案往往不是硬调MERGE参数,而是回到业务语义:用INSERT + 捕获DUP_VAL_ON_INDEX异常再UPDATE,或者对关键路径加SELECT ... FOR UPDATE。MERGE的简洁性背后,藏着对数据干净度、并发模型和驱动行为的多重隐性依赖。

















