insert ... on duplicate key update 仅在存在唯一索引或主键且插入值触发冲突时生效;无冲突约束则退化为普通insert,不报错也不更新。

INSERT ON DUPLICATE KEY UPDATE 什么情况下才生效
它只在插入时触发唯一约束(UNIQUE 或 PRIMARY KEY)冲突时才执行更新逻辑。如果表没有定义任何唯一索引或主键,INSERT ... ON DUPLICATE KEY UPDATE 就退化为普通 INSERT,不会报错但也不会更新——这点常被忽略。
常见错误现象:Duplicate entry 'xxx' for key 'PRIMARY' 出现了,但 UPDATE 部分没执行,大概率是字段没加 UNIQUE 索引,或者冲突的列不在索引覆盖范围内。
- 必须确保冲突列上有
UNIQUE或PRIMARY KEY约束(单列或联合索引均可) - 联合唯一索引下,只有所有列值完全匹配才会触发更新,少一个都不行
- 如果用的是
INSERT IGNORE替代,它会静默跳过冲突,但不会做任何更新操作
UPDATE 子句里能用哪些值
ON DUPLICATE KEY UPDATE 后面的赋值表达式,支持当前行的字段名、字面量、函数,也支持 VALUES(col_name) 引用本次 INSERT 中试图插入的值。
比如想保留原值不变,又不想写死默认值,可以写 col = col;想把计数器 +1,就写 count = count + 1;想用新值覆盖旧值,最稳妥写法是 name = VALUES(name)。
-
VALUES(col)是安全引用方式,避免因字段重名或别名导致歧义 - 不能在
UPDATE子句里引用其他表,不支持JOIN或子查询(MySQL 8.0.19+ 对部分子查询有限支持,但生产环境建议避开) - 如果更新字段有
DEFAULT值且未显式指定,不会自动填充,默认保持原值
和 REPLACE INTO 的关键区别在哪
REPLACE INTO 不是“更新”,而是“删+插”:先尝试删除冲突行,再插入新行。这会导致自增 ID 跳变、触发 DELETE 和 INSERT 触发器、外键级联行为更复杂。
而 INSERT ... ON DUPLICATE KEY UPDATE 是纯更新,不改变行物理位置,自增 ID 不变,只触发 UPDATE 触发器(如果有)。
- 当表有多个唯一索引时,
REPLACE INTO可能因匹配到不同索引而意外删掉不该删的行 -
ON DUPLICATE KEY UPDATE只响应第一个匹配的唯一冲突(按索引创建顺序),行为更可预测 - 性能上,
UPDATE比DELETE+INSERT开销小,尤其在大字段或有全文索引的表中更明显
批量插入时要注意的坑
一次 INSERT 多行数据时,每个冲突行独立判断是否触发 UPDATE,但所有行共用同一个语句上下文——这意味着 VALUES(col) 总是指向“当前正在处理的那行”的值,不是第一行或最后一行。
常见错误:用 LAST_INSERT_ID() 获取自增 ID,结果只返回第一条成功插入(或更新)的 ID,后续行不更新该函数值。
- 批量插入 + 更新场景下,不要依赖
ROW_COUNT()判断“更新了几行”——它返回的是“变更行数”,插入算 1,更新也算 1,无法区分 - 如果某一行违反了非唯一约束(如
CHECK或外键),整个语句会失败,不会部分执行 - 事务中使用时,确保隔离级别不影响冲突判断(例如
READ COMMITTED下可能因并发插入看到不同状态)
真正容易被绕开的点是:你以为冲突了,其实没建对索引;你以为更新了,其实写成了 col = col 这种无意义赋值;你以为批量安全,结果某一行悄悄违反了外键约束导致整批回滚。











