merge into 是 oracle 最稳妥的 upsert 方案,自 9i 起原生支持,通过 on 子句基于唯一约束字段原子化执行匹配更新或不匹配插入,避免竞态与重复逻辑,但必须确保 on 条件字段具备主键或唯一索引,否则触发 ora-30926 错误。

直接用 MERGE INTO 是最稳妥的选择
Oracle 从 9i 开始就原生支持 MERGE INTO,它在一个原子语句里完成“匹配则更新、不匹配则插入”,避免了竞态条件和重复逻辑。别再手写 INSERT + EXCEPTION WHEN DUP_VAL_ON_INDEX 或先 UPDATE 再查 SQL%ROWCOUNT —— 那些方式在并发场景下容易丢数据或报错。
关键点在于:ON 子句必须基于唯一约束字段(如主键或唯一索引列),否则可能触发多行匹配,导致 ORA-30926 错误。
-
USING后可以是表、视图,也可以是子查询甚至DUAL(用于单行 upsert) - 更新时建议显式列出所有要改的字段,避免漏写或误覆盖
- 插入字段列表和
VALUES必须严格一一对应,顺序不能错 - 如果源数据来自子查询,注意别漏掉
WHERE条件,否则可能批量误操作
用 INSERT ... EXCEPTION 方式要注意锁和异常类型
这种“先插后捕错再更新”的写法在低并发、单行场景下还能凑合,但实际生产中风险不小:插入失败时,事务已持锁;而 DUP_VAL_ON_INDEX 只捕获唯一键冲突,如果表上有多个唯一约束(比如联合唯一索引),得额外判断 SQLCODE 才能区分是哪个索引冲突。
更麻烦的是,它无法处理“匹配多行”的情况 —— 比如只靠 colour 字段做条件,但该字段没建唯一索引,UPDATE 就可能改掉多条记录,而 SQL%ROWCOUNT = 0 的判断就失效了。
- 必须确保
UPDATE的WHERE条件能精准定位一行(通常依赖主键或唯一键) - 异常块里不能只写
UPDATE,得加EXCEPTION WHEN NO_DATA_FOUND THEN NULL;防止更新没命中还抛错 - 如果业务允许,优先给 upsert 字段建唯一索引,否则
MERGE的ON条件会报错
多字段 upsert 时,WHEN MATCHED THEN UPDATE SET 的字段顺序无关,但值来源要明确
MERGE 里 UPDATE SET 和 INSERT VALUES 都支持从 USING 子句的别名取值,也支持函数、表达式甚至 NVL() 这类空值处理。常见需求比如“有新值就用新值,否则保留旧值”,就得靠 NVL(s.col, t.col) 或 COALESCE。
注意:更新子句里不能直接引用目标表别名的字段做计算(比如 t.salary * 1.1),必须通过源别名或子查询提供上下文,否则会报 ORA-00904。
- 时间戳字段常用
SYSDate或CURRENT_TIMESTAMP,别用SYSDATE拼字符串 - 如果要根据源字段是否为 NULL 决定是否覆盖,
NVL(s.name, t.name)比CASE WHEN s.name IS NOT NULL THEN s.name ELSE t.name END更简洁 - 涉及数值累加(如计数器)时,务必确认源字段非空,否则
t.count = t.count + s.delta可能变成NULL
并发环境下,MERGE 不自动加锁,需配合应用层控制
MERGE 本身是原子操作,但 Oracle 不会对 ON 条件涉及的行自动加唯一键锁(不像 SELECT FOR UPDATE)。这意味着两个并发事务同时对同一键执行 MERGE,可能都走 NOT MATCHED 分支,最终插入重复数据 —— 前提是那个键没建唯一索引。
所以真正保险的做法是:确保 ON 条件字段有唯一约束(主键或唯一索引),这样第二次插入会直接报 ORA-00001,由应用捕获并重试,而不是靠数据库自己“协调”。
- 没有唯一约束的字段,
MERGE会报ORA-30926: unable to get a stable set of rows in the source tables - 高并发 upsert 场景下,建议在应用层加分布式锁,或用带重试的存储过程封装
MERGE - 测试时别只跑单线程,用
UTL_HTTP或外部脚本模拟并发请求,才能暴露真实问题
实际写法复杂度主要卡在 ON 条件的设计和唯一性保障上,不是语法本身。很多人卡在“为什么明明写了唯一索引,MERGE 还报错”,最后发现是索引建在了函数表达式上,或者用了可空字段组合 —— 这些细节比怎么写 WHEN MATCHED 更容易出事。











