mysql主键冲突报错1062需显式处理:用insert ignore忽略、on duplicate key update更新或replace into替换;后者会改变自增id且慎用;on duplicate key update需注意NULL和表达式逻辑,批量插入无法直接定位冲突行。

INSERT 时主键冲突报错 1062 Duplicate entry 怎么办
MySQL 遇到主键或唯一索引重复时,直接抛出 Error 1062 (23000): Duplicate entry 'xxx' for key 'PRIMARY',默认中断执行。这不是语法错误,而是约束校验失败,必须明确告诉 MySQL「冲突时想怎么处理」。
常见做法有三种,选哪个取决于业务语义:
- 忽略冲突:用
INSERT IGNORE,冲突时静默跳过整行,不报错也不插入 - 存在则更新:用
INSERT ... ON DUPLICATE KEY UPDATE,冲突时执行指定字段更新(如更新updated_at或累加计数) - 强制替换:用
REPLACE INTO,本质是先 delete 再 INSERT,会触发自增 ID 变化和外键级联动作,慎用
注意:INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 只对主键(PRIMARY KEY)和唯一索引(UNIQUE KEY)生效,普通索引无效。
ON DUPLICATE KEY UPDATE 的字段更新逻辑容易踩坑
这个语法看似简单,但更新行为和 NULL、默认值、表达式有关,不是无脑覆盖:
- 如果更新子句中写
col = col + 1,即使col原值为NULL,结果也是NULL(因为NULL + 1 = NULL),需用IFNULL(col, 0) + 1 - 不能在
UPDATE子句里引用刚插入的值(比如INSERT ... VALUES (1, 'a') ON DUPLICATE KEY UPDATE name = VALUES(name)是合法的,但VALUES(id)在更新侧不可用) - 如果表有多个唯一键,只要任意一个命中就会触发更新;但更新只作用于 SET 后列出的字段,其余字段不变
INSERT INTO user_stats (user_id, login_count, last_login) VALUES (123, 1, NOW()) ON DUPLICATE KEY UPDATE login_count = IFNULL(login_count, 0) + 1, last_login = NOW();
批量插入时部分失败,如何知道哪几行冲突了
MySQL 默认不返回冲突详情,INSERT ... ON DUPLICATE KEY UPDATE 执行后,mysql_affected_rows()(C API)、cursor.rowcount(Python)或 mysqli_affected_rows() 返回的是「实际变更行数」:新插入算 1 行,更新算 2 行,忽略算 0 行。但不会告诉你哪条数据被忽略或更新。
- 若需定位具体冲突值,得在应用层提前
select检查,或用临时表 +LEFT JOIN对比 - 开启
log_warnings = 2并配合慢日志,可在错误日志里看到部分冲突提示(不推荐用于生产排查) - 更稳妥的方式是拆成单条带
INSERT ... SELECT ... WHERE NOT EXISTS,虽然性能差,但能用SELECT显式控制条件
用事务包装冲突操作时,锁行为要注意
主键冲突本身会触发间隙锁(gap lock)或记录锁(record lock),尤其在可重复读(REPEATABLE READ)隔离级别下:
-
INSERT IGNORE和ON DUPLICATE KEY UPDATE都会在冲突路径上加锁,防止其他事务同时插入相同主键 - 如果多个并发线程反复尝试插入同一主键,可能引发死锁(比如 A 锁了 gap,B 也锁了 gap,又各自试图插入对方已占的记录)
- 避免长事务包裹这类操作;高并发场景下,优先用应用层幂等设计(如先查后插+乐观锁),而非依赖数据库冲突处理
真正麻烦的从来不是语法怎么写,而是没意识到:一次 INSERT ... ON DUPLICATE KEY UPDATE 在并发下可能锁住一整个索引区间,而你只想要更新一行。