原生UPSERT是并发下避免竞态的必要手段,因其原子性确保冲突检测与操作由数据库引擎一次性完成;但必须依赖唯一约束,否则语法无效或行为异常。

直接用数据库原生 UPSERT 语法,就能避开“先 SELECT 再 INSERT”引发的竞态问题——这不是优化建议,而是并发场景下的必要做法。
为什么先查后插在高并发下必然出错
因为 SELECT 和后续 INSERT 是两个独立语句,中间存在时间窗口:A 事务查完没数据,正准备插入时,B 事务已插入成功,A 就会重复插入(报唯一键冲突)或被阻塞后失败。即使加 SELECT ... FOR UPDATE,也会显著降低吞吐,且无法覆盖所有隔离级别下的边界情况。
关键点在于:只有原子性写入操作才能真正规避竞态。
- MySQL 的
INSERT ... ON DUPLICATE KEY UPDATE是单条语句,冲突检测和更新/插入由引擎内部原子完成 - PostgreSQL 的
INSERT ... ON CONFLICT DO UPDATE同样不依赖外部判断,冲突目标列必须有唯一约束,否则语法无效 - SQL Server 的
MERGE在正确使用WITH (HOLDLOCK)提示时可保证原子性,但语法容错率低,稍有遗漏就退化为非原子行为 - Oracle 和 SQLite 的
MERGE INTO/INSERT ... ON CONFLICT也都要求ON条件基于唯一索引,否则直接报错(如 Oracle 的ORA-30926)
各数据库 UPSERT 必须满足的前置条件
所有主流数据库的 UPSERT 都不是“无条件可用”,它们共同依赖一个底层机制:唯一性约束。没有它,语法要么静默失效,要么报错。
- MySQL:必须在目标列上有
PRIMARY KEY或UNIQUE索引,否则ON DUPLICATE KEY UPDATE不触发更新,只插入(可能重复) - PostgreSQL:
ON CONFLICT (col)中的col必须是主键、唯一索引列,或明确写出约束名(ON CONFLICT ON CONSTRAINT idx_name);联合唯一索引要严格按定义顺序写,比如索引是(tenant_id, code),就不能只写ON CONFLICT (code) - SQL Server:
MERGE的ON条件字段必须能唯一定位目标行,否则报错 “The MERGE statement attempted to update or delete the same row more than once” - Oracle:
MERGE INTO的ON子句若匹配到多行,直接抛ORA-30926;必须确保条件字段具备唯一约束
容易被忽略的语义陷阱
UPSERT 不是万能胶水,几个典型误用会导致逻辑错误而非语法错误:
- MySQL 中写
name = name而非name = VALUES(name):这会让字段值不变,看起来“更新了”,实际什么都没改 - PostgreSQL 中在
DO UPDATE SET后加WHERE excluded.updated_at > users.updated_at,但忘了excluded.updated_at是本次插入值,而users.updated_at是当前行值——这个 WHERE 是生效的,但容易写反逻辑 - SQLite 中用
excluded.col时误写成EXCLUDED.col(大小写敏感),会报错no such column: EXCLUDED.col - 所有数据库中,如果源数据来自子查询且未加
LIMIT 1或去重逻辑,可能因多行匹配导致 UPSERT 批量执行,超出预期
并发下真正可靠的写法长什么样
可靠不等于复杂,而是把约束、语法、意图三者对齐。例如 PostgreSQL 插入用户并累加登录次数:
INSERT INTO users (id, name, email, login_count, updated_at)
VALUES (100, 'Alice', 'a@b.com', 1, EXTRACT(EPOCH FROM NOW()))
ON CONFLICT (id) DO UPDATE
SET login_count = users.login_count + EXCLUDED.login_count,
updated_at = EXTRACT(EPOCH FROM NOW());
这里 ON CONFLICT (id) 明确绑定主键,EXCLUDED.login_count 确保用的是本次输入值,users.login_count 引用原值做累加——每一步都不可省略,少一个就可能丢数据或覆盖错。
最常被跳过的其实是建约束这步:很多人写了 UPSERT 语句却忘了在表上加 UNIQUE,然后花半天排查为什么“不更新”。

















