mysql 8.0.19+ 仅在 ndb cluster 中支持 merge 语句,innodb 表至今不支持;替代方案为 insert ... on duplicate key update 或 replace into。

MySQL 不支持 MERGE,得换写法
MySQL 从 8.0.19 开始才提供 MERGE 语句(仅限 NDB Cluster),普通 InnoDB 表至今不支持。所以你在 MySQL 里直接写 MERGE INTO ... 会报错:ERROR 1064 (42000): You have an error in your SQL syntax。别浪费时间查文档确认语法——先看引擎和版本,再决定用什么替代。
- 检查版本:
SELECT VERSION(); - 查表引擎:
SHOW CREATE TABLE your_table; - 如果用的是 InnoDB + MySQL 8.0.x,老实用
INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO - 如果是 PostgreSQL,才有原生
MERGE;SQL Server 也支持,但语法细节不同
PostgreSQL 的 MERGE 要严格匹配 ON 条件
PostgreSQL 15+ 支持 MERGE,但它不像某些 ORM 想象中那么“智能”:ON 子句必须基于目标表的主键或唯一约束列,否则会报错 ERROR: there is no unique or exclusion constraint matching the ON condition。它不是靠 WHERE 推断,而是真要靠约束来保证“一行对一行”。
- 错误写法:
MERGE INTO users u USING (VALUES ('x@example.com')) AS v(email) ON u.email = v.email—— 如果email没建UNIQUE索引,就失败 - 正确前提:确保
ON中的列在目标表上有UNIQUE或PRIMARY KEY约束 -
WHEN MATCHED THEN UPDATE只能更新匹配到的那行,不能加额外WHERE过滤(想过滤得写进ON或用子查询) - 性能上,MERGE 会走索引查找,但若 ON 条件没走索引,可能全表扫描——务必 explain analyze
SQL Server 的 MERGE 有隐藏事务风险
SQL Server 的 MERGE 看似方便,但一个常见坑是:当源数据里有重复 key、且 ON 匹配到多行时,会直接报错 The MERGE statement attempted to UPDATE or DELETE the same row more than once。这不是警告,是硬性中断,而且整个语句回滚——哪怕你只打算 upsert 100 行,其中 1 行重复,其余 99 行也全白干。
- 必须提前去重源数据,例如用
ROW_NUMBER() OVER (PARTITION BY key_col ORDER BY priority) = 1 -
OUTPUT子句能返回操作类型(INSERT/UPDATE),但要注意它不包含被跳过的行(比如 MATCHED 但没满足 WHEN 条件的) - 避免在 MERGE 中调用函数或子查询作为 ON 条件的一部分,SQL Server 优化器有时会误判执行计划,导致意外扫描
- 如果只是单行 upsert,
IF EXISTS ... UPDATE ELSE INSERT反而更可控
跨数据库可移植的稳妥方案:UPSERT 模式
真要写一次代码跑多个数据库,别碰 MERGE。PostgreSQL 用 INSERT ... ON CONFLICT,MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE,SQL Server 用 MERGE 或 IF NOT EXISTS,三者语法无法统一。最稳的办法是拆成两步逻辑:先尝试 UPDATE,检查 ROWCOUNT 或 affected_rows,为 0 再 INSERT。
- 应用层控制比依赖数据库语法更可靠,尤其涉及复杂业务判断时(比如“只在 status=active 时才更新”)
- 注意并发:UPDATE + INSERT 之间存在竞态窗口,需加唯一索引兜底,否则可能报 duplicate key 错误
- ORM 如 SQLAlchemy 提供了
insert().on_conflict_do_update()这类抽象,但底层仍是数据库特有语法,生成的 SQL 并不通用 - 别为了“看起来简洁”硬套 MERGE——线上出问题时,查日志、定位冲突点、补数据,都比读一段嵌套 MERGE 更快










