PostgreSQL用INSERT ON CONFLICT替代MERGE,仅支持冲突更新;SQL Server MERGE强制要求WHEN MATCHED和WHEN NOT MATCHED双分支,ON条件含NULL易致全插入;Oracle MERGE支持单分支但函数索引缺失会导致全表扫描。

MERGE 不是“写对语法就能跑”,它在 SQL Server、Oracle、PostgreSQL 中行为差异大,且稍有疏忽就会 silently 错误或性能崩盘——必须按数据库方言分别处理,不能一套模板打天下。
SQL Server:WHEN NOT MATCHED 和 WHEN MATCHED 都不能少
SQL Server 强制要求 MERGE 必须同时包含 WHEN MATCHED THEN UPDATE 和 WHEN NOT MATCHED THEN INSERT 两个分支,漏掉任一个直接报错:Incorrect syntax near the keyword 'MERGE' 或更隐蔽的 The MERGE statement attempted to UPDATE or DELETE the same row more than once。
-
ON t.id = s.id中若id列允许 NULL,匹配恒为 UNKNOWN,所有行都进 INSERT 分支 → 重复插入 - 想避免无意义更新(比如字段值没变也触发 UPDATE),不能在
WHEN MATCHED后加AND t.name != s.name—— SQL Server 不支持该写法;得用UPDATE SET name = CASE WHEN t.name != s.name THEN s.name ELSE t.name END - 批量场景下,
USING必须走临时表(如#staging)而非逐行参数拼接;否则执行计划反复生成,1000 行以上就明显卡顿 - 高并发必须加
WITH (HOLDLOCK),否则大概率出现丢失更新或双插入
Oracle:别把 MERGE 套在 FOR LOOP 里
Oracle 允许只写 WHEN MATCHED 或只写 WHEN NOT MATCHED,但性能陷阱藏在数据组织方式里——把 MERGE 放进游标循环,等于每行都硬解析一次,10k 行可能从毫秒级拖到秒级。
- 正确做法是先
BULK COLLECT到 PL/SQL 集合,再用FORALL批量执行;或用USING (WITH src AS (...) SELECT * FROM src)直接喂数据,免建 GTT -
ON (UPPER(t.email) = UPPER(s.email))这类写法会绕过索引,除非你显式建了函数索引且统计信息最新,否则执行计划里必见FULL TABLE SCAN - 源数据中
id为 NULL 时,t.id = s.id返回 UNKNOWN → 匹配失败;需提前WHERE s.id IS NOT NULL过滤,或改用ON (t.id = s.id OR (t.id IS NULL AND s.id IS NULL)) -
UPDATE SET status = s.status会把 NULL 写进目标表;要保留原值就得写成status = NVL(s.status, t.status)
PostgreSQL:用 INSERT ON CONFLICT,别碰 MERGE
PostgreSQL 根本不支持 MERGE 语句。强行移植 SQL Server 的 MERGE 模板只会报错:syntax error at or near "MERGE"。它的标准 Upsert 是 INSERT ... ON CONFLICT,底层依赖唯一约束,更轻量、更可控。
-
ON CONFLICT (id)中的id必须是已定义的主键或唯一索引列,否则报错:there is no unique or exclusion constraint matching the ON CONFLICT specification -
EXCLUDED是伪表,代表本次冲突被拒绝插入的那行;不能写成VALUES或引用外部变量,比如DO UPDATE SET updated_at = NOW() WHERE users.score != EXCLUDED.score是合法的 - 没有
WHEN MATCHED的概念,所以不存在“分支遗漏”问题;但如果你需要“仅更新不插入”,得用UPDATE ... FROM+ 子查询替代 - 避免在
ON CONFLICT中用表达式(如ON CONFLICT (LOWER(email))),它无法利用普通索引,必须配合CREATE UNIQUE INDEX ... ON table ((LOWER(email)))
跨库移植最易踩的三个坑
同一份业务逻辑,在 SQL Server 写完迁到 Oracle 或 PostgreSQL,90% 的失败不是语法报错,而是行为静默偏移。
-
USING子句:SQL Server 接受(SELECT @id AS id),Oracle 必须带FROM DUAL,PostgreSQL 直接报错——得统一改用(VALUES (@id)) AS s(id) - NULL 处理:SQL Server 和 Oracle 的
ON条件对 NULL 比较结果都是 UNKNOWN,但 PostgreSQL 的ON CONFLICT在唯一索引上对 NULL 的处理是“NULL 不冲突”,即多行 NULL 可共存,这点极易引发数据一致性问题 - 触发器响应:SQL Server 的
MERGE可能拆成多个语句执行,导致触发器触发次数与单独INSERT/UPDATE不一致;Oracle 和 PostgreSQL 的对应机制也不同,不能假设行为一致
真正难的从来不是写出第一版 MERGE,而是确认它在并发、NULL、索引失效、触发器介入等边界条件下仍稳定输出预期结果——这些点不会报错,但会让数据在几周后突然对不上。

















