MySQL中INSERT…ON DUPLICATE KEY UPDATE在存在唯一索引或主键冲突时触发更新,语法为INSERT INTO tbl(col…) VALUES(…) ON DUPLICATE KEY UPDATE col=VALUES(col);需确保表有对应唯一约束,VALUES()引用本次插入值,多唯一索引时仅按索引顺序匹配首行更新。

MySQL 的 INSERT ... ON DUPLICATE KEY UPDATE 怎么写
当表有唯一索引(UNIQUE)或主键(PRIMARY KEY),且想避免重复插入报错、转而更新已有行时,直接用这个语法最稳妥。它不是标准 SQL,但 MySQL 原生支持,语义清晰、执行原子。
常见错误是只写 UPDATE 部分却漏掉冲突触发条件——必须依赖已定义的唯一约束,否则不会触发更新。例如:
INSERT INTO users (id, name, email, updated_at) VALUES (123, 'Alice', 'alice@ex.com', NOW()) ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email), updated_at = NOW();
-
VALUES(col)表示本次INSERT中该列打算插入的值,比直接写变量名更安全(尤其批量插入时) - 不能在
UPDATE子句里引用未在INSERT列表中出现的列(比如INSERT没写status,就不能在UPDATE里设status = 1) - 如果冲突由联合唯一索引引发(如
UNIQUE (user_id, type)),只要任一组合重复,就会触发更新
PostgreSQL 怎么实现“存在则更新”
PostgreSQL 不支持 ON DUPLICATE KEY UPDATE,得用 INSERT ... ON CONFLICT,这是它的标准方案,也更灵活。
关键点在于:必须显式指定冲突目标(ON CONFLICT (column) 或 ON CONFLICT ON CONSTRAINT constraint_name),否则报错 ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification。
- 推荐用约束名写法,比如
ON CONFLICT ON CONSTRAINT users_pkey或ON CONFLICT ON CONSTRAINT users_email_key,可读性和维护性更好 - 若想忽略冲突不报错也不更新,用
DO NOTHING;要更新就用DO UPDATE SET ... -
EXCLUDED是个伪表,代表本次被拒绝插入的那行数据,类似 MySQL 的VALUES(),比如email = EXCLUDED.email
示例:
INSERT INTO users (id, name, email, updated_at) VALUES (123, 'Alice', 'alice@ex.com', NOW()) ON CONFLICT ON CONSTRAINT users_pkey DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email, updated_at = NOW();
SQLite 的 INSERT OR REPLACE 为什么有时会删再插
INSERT OR REPLACE 看似简单,但它底层逻辑是:检测到唯一冲突时,先 DELETE 冲突行,再 INSERT 新行。这意味着:
- 自增主键(
INTEGER PRIMARY KEY)会分配新 ID,旧 ID 丢失——这不是“更新”,是“删+插” - 外键关联的子表记录若设了
ON DELETE CASCADE,会被连带删除 - 触发器(
AFTER INSERT)会执行两次(一次删、一次插),而AFTER UPDATE完全不触发
所以,除非你明确接受“ID 可能变”和“级联删除风险”,否则别把它当“UPSERT”用。真正安全的替代是 INSERT OR IGNORE + 单独 UPDATE,或 SQLite 3.24+ 的 INSERT ... ON CONFLICT(语法同 PostgreSQL)。
跨数据库兼容的“UPSERT”能写吗
不能写一份 SQL 在 MySQL/PG/SQLite 上都直接跑通。各数据库对冲突处理的语法、语义、触发条件都不一致,强行抽象容易出错。
实际项目中建议:
- ORM 层(如 SQLAlchemy、Django ORM、Prisma)封装了不同方言的 UPSERT 实现,优先用它们提供的
upsert()或on_conflict_do_update()方法 - 纯 SQL 场景下,按目标数据库选对应语法,不要试图“写一次到处用”
- 特别注意时间戳字段:MySQL 的
NOW()、PG 的CURRENT_TIMESTAMP、SQLite 的datetime('now')写法不同,且在ON DUPLICATE KEY UPDATE或DO UPDATE中行为也可能差异
最易被忽略的是约束定义本身——没有唯一索引或主键,所有 UPSERT 语法都无效。上线前务必确认表结构里真有对应约束,而不是只靠应用层“以为不会重复”。


















