merge 是 oracle 唯一安全的高并发 upsert 原生方案,on 必须基于唯一索引,否则报 ora-30926;需过滤 null、显式处理 update 中 null 值,避免清空字段;推荐批量操作而非循环,控制单次行数≤5000。

MERGE 是 Oracle 存储过程中唯一能安全应对高并发 upsert 的原生方案,其他写法(如 INSERT + EXCEPTION 或 UPDATE + IF SQL%ROWCOUNT = 0)在并发下必然丢数据或报 ORA-00001 / ORA-01403。
ON 条件必须基于唯一索引,否则直接报 ORA-30926
Oracle 要求 MERGE 的 ON 子句所用字段必须有主键或唯一索引,否则无法保证“一行匹配”,触发 ORA-30926: unable to get a stable set of rows in the source tables。这不是警告,是硬性失败。
- 错误写法:
ON (t.email = s.email)—— 若email列没建唯一索引,哪怕只插入两条相同 email 就崩 - 正确写法:
ON (t.id = s.id)或ON (t.org_id = s.org_id AND t.code = s.code),且这两个字段组合必须落在同一个唯一约束里 - 别指望函数索引兜底:
ON (UPPER(t.email) = UPPER(s.email))即使建了函数索引,也得确认统计信息最新、且执行计划真用了它;否则就是全表扫描 + HASH JOIN
批量 upsert 别写 FOR LOOP,用 BULK COLLECT + FORALL 或 WITH
把 MERGE 套进游标循环里,等于每行都做一次硬解析 + 执行计划生成 + 日志刷盘,1000 行可能耗时秒级;而一次性合并只要毫秒级。
- ❌ 错误模式:
FOR rec IN (SELECT id, name FROM src) LOOP MERGE INTO t USING (SELECT rec.id id, rec.name name FROM DUAL) s ON ... END LOOP; - ✅ 推荐方式一(轻量):
USING (WITH src AS (SELECT id, name FROM your_source_table WHERE id IS NOT NULL) SELECT * FROM src)—— 避免建 GTT,又支持 WHERE 过滤 NULL - ✅ 推荐方式二(大数据量):先
BULK COLLECT到关联数组,再用FORALL i IN 1..arr.COUNT MERGE ... USING (SELECT arr(i).id id, arr(i).name name FROM DUAL) ... - ⚠️ 务必过滤 NULL:
WHERE s.id IS NOT NULL,因为t.id = s.id在任一为 NULL 时结果为 UNKNOWN,全部落入 INSERT 分支
UPDATE 分支必须显式处理 NULL,否则线上清空字段
Oracle MERGE 的 UPDATE SET 默认不跳过 NULL —— 源数据某列为 NULL,目标字段就被设成 NULL,这是生产事故最高发点。
- ❌ 危险写法:
UPDATE SET name = s.name, status = s.status—— 只要s.name是 NULL,t.name就被清空 - ✅ 安全写法一(保留原值):
name = NVL(s.name, t.name), status = NVL(s.status, t.status) - ✅ 安全写法二(仅非空更新):
UPDATE SET name = s.name, status = s.status WHERE s.name IS NOT NULL AND s.status IS NOT NULL - ✅ 增量更新(减少 redo):
updated_at = CASE WHEN s.name != t.name OR s.status != t.status THEN SYSDATE ELSE t.updated_at END
高并发下仍需注意锁粒度与事务边界
MERGE 是原子语句,但不等于无锁。ON 字段若没索引,会升级为表锁;即使有索引,大量并发写同一分区或热点 ID 仍可能引发争用。
- 源数据尽量按目标表主键排序后再喂入,减少索引跳跃访问
- 单次 MERGE 处理行数建议控制在 5000 行以内,避免事务日志暴涨或锁等待超时
- 不要在存储过程中嵌套 COMMIT —— MERGE 自身不提交,得由外层事务控制,否则无法回滚
- 如果业务允许,给 upsert 字段加唯一索引不是可选项,是前提条件;没它,MERGE 就跑不起来











