mysql中实现upsert最常用方式是insert … on duplicate key update,前提是表必须有primary key或unique约束,否则不触发更新;它根据主键或唯一索引冲突自动选择插入或更新,并支持values()引用新值及原字段运算。

MySQL 中用 INSERT ... ON DUPLICATE KEY UPDATE 实现 upsert
这是最常用、最直接的方式,但前提是表必须有 PRIMARY KEY 或 UNIQUE 约束,否则 MySQL 不知道“重复”指什么。
常见错误是只建了普通索引(INDEX),没建 UNIQUE INDEX,结果语句执行不报错,但始终走插入逻辑,根本不会触发更新。
- 确保目标字段(如
user_id)上有UNIQUE或主键约束 -
ON DUPLICATE KEY UPDATE只对违反唯一约束的那条记录生效,不影响其他行 - 更新部分可以引用原值,比如
score = score + 1,也可以用VALUES(column_name)引用 INSERT 中的值 - 如果有多列唯一约束(如
UNIQUE (a, b)),只要其中一组值重复就触发更新
INSERT INTO users (id, name, email, updated_at) VALUES (123, 'Alice', 'a@example.com', NOW()) ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email), updated_at = NOW();
PostgreSQL 中用 INSERT ... ON CONFLICT DO UPDATE
PostgreSQL 的语法更明确,必须显式指定冲突目标(ON CONFLICT (column)),不能靠隐式推断。
容易踩的坑是写成 ON CONFLICT DO UPDATE SET ... 而漏掉括号里的冲突列,会报错 ERROR: ON CONFLICT clause requires a conflict target。
- 冲突目标必须是
UNIQUE或PRIMARY KEY约束覆盖的列(或列组合) - 更新子句中若要引用被插入的行,需用
EXCLUDED别名,比如name = EXCLUDED.name - 不能在
DO UPDATE中引用其他表,除非用子查询(但性能差,慎用) - 如果想跳过更新(即“有则忽略”),用
DO NOTHING更高效
INSERT INTO users (id, name, email, updated_at) VALUES (123, 'Alice', 'a@example.com', NOW()) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email, updated_at = NOW();
SQL 标准兼容方案:MERGE 语句(Oracle / SQL Server / PostgreSQL 9.5+)
MERGE 是 SQL:2003 标准语法,语义清晰,但各数据库支持程度和细节差异大,实际跨库迁移时反而容易出问题。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
比如 SQL Server 要求 WHEN MATCHED 和 WHEN NOT MATCHED 都必须存在;Oracle 不允许在 UPDATE SET 中直接用源表字段名,得加别名;PostgreSQL 的 MERGE 直到 15 才稳定支持,且不支持 RETURNING 子句。
- 不是所有数据库都支持
MERGE(如 MySQL 完全不支持) - 即使支持,语法细节(如是否允许
WHERE条件、能否更新多表)也各不相同 - 调试困难:错误信息往往指向整个
MERGE块,而不是具体子句 - 对简单 upsert 场景,不如数据库原生语法直观可靠
应用层做“先查后插/更新”为什么通常不推荐
看似可控,实则引入竞态条件:两次请求几乎同时执行,可能都查不到,然后都插入,导致重复数据。
除非你加了应用层分布式锁(如 Redis 锁),或者数据库事务隔离级别设为 SERIALIZABLE(性能代价高),否则无法真正避免冲突。
- 查 + 插/更新 至少 2 次网络往返,延迟翻倍
- 即使加了事务,在高并发下仍可能因幻读或间隙锁行为异常
- 业务逻辑分散:判断逻辑在代码里,维护成本高于一条声明式 SQL
- 唯一能规避竞态的方式是把判断和操作压进单条原子 SQL —— 这正是
ON DUPLICATE KEY UPDATE和ON CONFLICT存在的意义
真正要注意的是冲突判定依据是否设计合理。比如用邮箱做唯一键,但没处理大小写或空格,就会让两条本该合并的记录各自插入。这类问题不在 SQL 写法本身,而在数据建模阶段就埋下了隐患。










