mysql中insert ... on duplicate key update实现幂等的前提是表必须有primary key或unique索引,否则不触发更新而退化为普通insert;它通过唯一约束冲突自动切换为update,返回值1表示插入、2表示更新、0表示无实际变更,且不触发insert触发器。

MySQL用 INSERT ... ON DUPLICATE KEY UPDATE 实现幂等
这是最常用也最稳妥的方式,前提是表里有 UNIQUE 或 PRIMARY KEY 约束(比如 id 或 email 字段),否则无法触发“冲突”逻辑。
常见错误是只建了普通索引没加 UNIQUE,结果 ON DUPLICATE KEY UPDATE 完全不生效,还误以为插入成功了。
- 必须确保冲突字段上有
UNIQUE或PRIMARY KEY约束,仅INDEX不够 - 如果只想插入、不想更新,可以把
UPDATE部分设为空操作,例如:INSERT INTO user (id, name) VALUES (1, 'Alice') ON DUPLICATE KEY UPDATE id=id - 注意:
ON DUPLICATE KEY UPDATE会返回影响行数为2(插入)或0(冲突后更新),但实际是否新增需看业务逻辑是否依赖该返回值
PostgreSQL用 INSERT ... ON CONFLICT DO NOTHING / UPDATE
PostgreSQL 的语法更语义清晰,ON CONFLICT 明确指定冲突目标(比如 ON CONFLICT (email)),比 MySQL 更可控。
容易踩的坑是漏写 WHERE 条件导致意外覆盖,或者在 DO UPDATE 中引用了未声明的别名。
- 冲突目标必须是约束名或列名;推荐显式写列名,例如:
ON CONFLICT (email) DO NOTHING - 如果要用
DO UPDATE,记得用EXCLUDED引用新值,比如:DO UPDATE SET name = EXCLUDED.name - 不能对表达式建唯一约束(如
UPPER(email))然后指望ON CONFLICT (UPPER(email))生效——必须用函数索引 + 约束名才能匹配
通用兜底方案:先 SELECT 再 INSERT(慎用)
没有唯一约束、又无法改表结构时,只能靠应用层“查+插”,但这不是真正幂等——并发下仍有竞态风险。
除非加锁(如 SELECT ... FOR UPDATE)或用分布式锁,否则 SELECT 和 INSERT 之间存在时间窗口,两个事务可能同时判断“不存在”,然后都插入成功。
- 单机简单场景可用:
SELECT 1 FROM user WHERE email = 'x@y.z',查不到再INSERT - 高并发必须配合行锁:
SELECT ... FROM user WHERE email = 'x@y.z' FOR UPDATE,但会降低吞吐 - 注意:某些 ORM(如 Django ORM)的
get_or_create()底层仍是这个逻辑,不要默认它线程安全
SQLite 的 INSERT OR IGNORE / REPLACE 的取舍
INSERT OR IGNORE 最接近“不存在才插入”,但要求目标列有 UNIQUE 约束;REPLACE 是删+插,会改变 rowid 且触发 DELETE 相关逻辑(比如外键级联、触发器),不是真正的“忽略”。
- 优先用
INSERT OR IGNORE,语义明确,性能好 - 避免
REPLACE,除非你清楚它会删除旧行(包括自增 ID 重置、触发器执行) - SQLite 不支持
ON CONFLICT的细粒度控制(如只 ignore 某个约束),所以约束设计要精准
SELECT + INSERT 是最常见的翻车点,尤其在迁移旧表或快速原型阶段容易忽略这点。










