MySQL的ON DUPLICATE KEY UPDATE可原子化实现upsert,需主键或唯一索引触发,用VALUES()引用插入值,支持批量且每行独立判断冲突。

直接用数据库原生的 UPSERT 语义,别在应用层做“先查后插”。这是最可靠、最高效、也最不容易出错的方式——因为冲突判断和写入动作必须原子化,而只有数据库能真正保证这点。
MySQL:用 ON DUPLICATE KEY UPDATE 替代手动判断
你写个 SELECT 再决定要不要 INSERT,中间永远存在竞态窗口。MySQL 的 ON DUPLICATE KEY UPDATE 在单条语句内完成“尝试插入→冲突则更新”的闭环,不依赖事务隔离级别,也不需要额外加锁。
- 必须确保目标列上有主键或
UNIQUE约束,否则不会触发更新逻辑 -
VALUES(column_name)是关键:它引用本次INSERT尝试中的值,不是当前行旧值 - 批量插入时,每行独立判断冲突,不会因某一行重复导致整批失败
- 注意自增 ID 行为:冲突时不会消耗新 ID,但
REPLACE INTO会删再插,ID 会跳变,慎用
PostgreSQL:必须显式指定 ON CONFLICT 的冲突目标
PostgreSQL 不接受模糊的列名推断。如果你写 ON CONFLICT (email),而表里有多个基于 email 的唯一约束(比如一个 UNIQUE(email),另一个是 UNIQUE(email, tenant_id)),PostgreSQL 可能报错或选错约束。
- 推荐始终用
ON CONFLICT ON CONSTRAINT constraint_name,约束名可通过\d table_name查看 -
EXCLUDED是只读虚拟表,代表“这次想插但没插进去的那行”,DO UPDATE SET name = EXCLUDED.name才是安全写法 - 如果只想忽略重复,用
DO NOTHING;但要注意它不返回受影响行数,应用层无法区分“真插入”和“被忽略” - 复合唯一键场景下,
ON CONFLICT (a, b)和ON CONFLICT ON CONSTRAINT idx_a_b效果等价,但后者更稳定
MyBatis 批量插入时怎么写 ON DUPLICATE KEY UPDATE
XML 映射里直接拼接是可行的,但要注意三件事:SQL 长度限制、参数绑定安全、以及 MySQL 对批量 ON DUPLICATE KEY UPDATE 的字段一致性要求。
- 不要在
UPDATE子句里混用VALUES(col)和字面量,比如status = 'processed'+updated_at = VALUES(updated_at)是允许的,但字段顺序和数量要对齐 - 单次批量建议控制在 500 行以内,避免 SQL 超过
max_allowed_packet - 若使用注解方式,需配合
@SelectKey或useGeneratedKeys处理主键回填——但ON DUPLICATE KEY UPDATE场景下,冲突行的主键已存在,无需生成 - 别用
<script></script>拼接字符串来构造动态列名,容易引发 SQL 注入;字段名应来自白名单配置
真正容易被忽略的是约束定义本身:很多团队线上表缺唯一索引,或者唯一性只靠应用层“自觉”,结果一上并发就出脏数据。UPSERT 再优雅,也救不了没建好索引的表。

















