mysql用insert ... on duplicate key update实现upsert,需唯一索引触发更新;postgresql用insert ... on conflict更灵活;sqlite推荐on conflict而非or replace以避免隐式删除风险;三者语法不兼容,须按数据库选型。

MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE 做 UPSERT
MySQL 没有标准的 UPSERT 语法,但 INSERT ... ON DUPLICATE KEY UPDATE 是最常用、最可靠的替代方案。它要求表上存在唯一索引(UNIQUE 或 PRIMARY KEY),否则不会触发更新逻辑。
常见错误现象:ERROR 1062 (23000): Duplicate entry 'xxx' for key 'PRIMARY' 出现,说明没加 ON DUPLICATE KEY UPDATE 子句,只写了普通 INSERT;或者唯一约束没建对,比如只在 id 上建了主键,但想根据 email 去去重更新,却没给 email 加 UNIQUE 索引。
- 必须确保冲突字段上有
UNIQUE或PRIMARY KEY约束,否则语句静默执行插入,不更新 -
VALUES()函数可引用本次插入值,比如UPDATE name = VALUES(name), updated_at = NOW() - 如果更新字段含自增列(如
id),不能写id = VALUES(id),会报错ERROR 1467 - 性能上,该语句是原子的,但高并发下可能因间隙锁引发等待,尤其在二级唯一索引上
示例:
INSERT INTO users (email, name, status) VALUES ('a@b.com', 'Alice', 'active')
ON DUPLICATE KEY UPDATE name = VALUES(name), status = VALUES(status), updated_at = NOW();
PostgreSQL 用 INSERT ... ON CONFLICT 实现真正 UPSERT
PostgreSQL 9.5+ 支持标准 SQL 的 ON CONFLICT 语法,比 MySQL 更灵活:能指定具体约束名、支持 DO NOTHING 和 DO UPDATE,还能用 EXCLUDED 表引用待插入行。
使用场景:你想按 (tenant_id, external_id) 联合唯一约束做 UPSERT,而不是单字段;或需要在冲突时跳过(DO NOTHING)而非强制更新。
- 必须显式指定冲突目标,如
ON CONFLICT (email)或ON CONFLICT ON CONSTRAINT users_email_key,不能只写ON CONFLICT -
DO UPDATE中不能直接写SET name = name,要写成SET name = EXCLUDED.name,否则会把原值赋给自己 - 如果
DO UPDATE中引用了未在INSERT列表中出现的字段(如只插email,但想在更新时设updated_at),需在INSERT中显式提供默认值或用DEFAULT - 注意
WHERE条件在DO UPDATE中是允许的,可用于避免无意义更新(如仅当新值非空才覆盖)
示例:
INSERT INTO users (email, name, status) VALUES ('a@b.com', 'Alice', 'active')
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name, status = EXCLUDED.status, updated_at = NOW();
SQLite 用 INSERT OR REPLACE 的隐式删除风险
SQLite 的 INSERT OR REPLACE 看似简单,但它不是真正的 UPSERT:遇到冲突时,SQLite 先删后插。这会导致外键级联、触发器、自增 ID 重分配等副作用,容易踩坑。
eSiteGroup站群管理系统是基于eFramework低代码开发平台构建,是一款高度灵活、可扩展的智能化站群管理解决方案,全面支持SQL Server、SQLite、MySQL、Oracle等主流数据库,适配企业级高并发、轻量级本地化、云端分布式等多种部署场景。通过可视化建模与模块化设计,系统可实现多站点的快速搭建、跨平台协同管理及数据智能分析,满足政府、企业、教育机构等组织对多站点统一管控的
常见错误现象:插入后发现 id 变了;关联表里本该保留的记录被 ON DELETE CASCADE 清掉了;触发器执行了两次(DELETE + INSERT 各一次)。
- 仅当表无外键引用、无复杂触发器、且接受 ID 变更时,才考虑用
INSERT OR REPLACE - 更安全的替代是
INSERT OR IGNORE+ 单独UPDATE,但需事务包裹(BEGIN IMMEDIATE)保证原子性 - SQLite 3.24+ 支持
INSERT ... ON CONFLICT(语法同 PostgreSQL),推荐优先使用,避免隐式删除 -
REPLACE不会触发UPDATE触发器,只触发INSERT和DELETE触发器
示例(推荐):
INSERT INTO users (email, name, status) VALUES ('a@b.com', 'Alice', 'active')
ON CONFLICT(email) DO UPDATE SET name = excluded.name, status = excluded.status;
跨数据库写法不兼容,别硬套一个 SQL
没有一种 SQL 写法能在 MySQL、PostgreSQL、SQLite 间通用。强行用 ON CONFLICT 在 MySQL 会报错 ERROR 1064;用 ON DUPLICATE KEY UPDATE 在 PostgreSQL 直接语法错误;INSERT OR REPLACE 在 PostgreSQL 和 MySQL 都不认。
实际项目中,最容易被忽略的是迁移或测试环境切换时的 SQL 兼容性问题——开发用 SQLite 跑通了,上线到 MySQL 就挂。
- ORM 层(如 Django
update_or_create、SQLAlchemyon_conflict_do_update)能屏蔽差异,但底层仍走各自方言 - 纯 SQL 场景下,必须按目标数据库选型写,不能靠注释或条件拼接“伪装兼容”
- CI 中建议用真实数据库跑集成测试,而不是只 mock 查询结果
- 如果必须手写跨库 SQL,优先拆成两步:
SELECT判断是否存在,再决定INSERT或UPDATE,虽慢但可控
事情说清了就结束









