insert ignore比on duplicate key update更少锁,因其冲突时直接跳过、不加x锁、不触发行更新、不维护二级索引日志;而后者即使伪更新(如id=id)也加x锁并可能引发间隙锁争用和索引分裂。

INSERT IGNORE 为什么比 ON DUPLICATE KEY UPDATE 更少锁
因为 INSERT IGNORE 在唯一键冲突时直接跳过,不加 X 锁、不触发行更新、也不维护二级索引变更日志;而 ON DUPLICATE KEY UPDATE 即使 UPDATE 子句没真正改字段(比如写成 id=id),也会对目标行加 X 锁,并可能引发索引分裂和 gap lock 争用。
常见错误现象:lock_mode X locks gap before rec insert intention waiting 出现在 SHOW ENGINE INNODB STATUS 中,说明多个事务在等同一个间隙锁——这几乎总是由 ON DUPLICATE KEY UPDATE 或 REPLACE INTO 引发的。
- 语义允许“冲突即丢弃”时,无条件选
INSERT IGNORE - 需返回是否插入成功,用
ROW_COUNT():返回1= 新增,0= 冲突忽略 - 不要用
ON DUPLICATE KEY UPDATE id=id这类伪更新——它照样锁行,且让优化器无法走覆盖索引
批量 INSERT 必须严格升序,否则间隙锁交叉等待
InnoDB 的间隙锁机制决定了:只要一批 INSERT VALUES () 中的主键或唯一键值乱序,就大概率触发交叉加锁。例如插入 (100, 'a'), (99, 'b'), (101, 'c'),事务 A 锁了 99–100 间隙,事务 B 锁了 100–101 间隙,互相等待形成锁等待甚至死锁(ERROR 1205)。
- 应用层插入前必须
ORDER BY id ASC,不能依赖数据库排序(INSERT SELECT ORDER BY不影响间隙锁行为) - 单批不超过
500行;超过则拆成多条语句,每条内部严格升序 - 若用雪花 ID 或 UUID,生成后立刻在内存排序,再拼 SQL
- 建表时确认
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,且没额外加UNIQUE索引(二级唯一索引会引发双倍间隙锁)
捕获 ERROR 1062 要精准,不能靠字符串匹配
INSERT IGNORE 会忽略所有警告级错误,不单是唯一冲突,容易掩盖字段截断、ENUM 越界等数据问题;而盲目 try...except 捕获所有 IntegrityError 又可能吞掉本该报警的异常。
- 只在“插入不存在记录、失败即忽略”的幂等场景下捕获
1062,比如去重日志写入、幂等注册 - 必须用错误码判断,而非字符串匹配(不同 MySQL 版本提示文本可能微调)
- Python 示例中应检查
e.args[0] == 1062,而不是"Duplicate entry" in str(e) - 避免在关键路径上依赖
ON DUPLICATE KEY UPDATE做“存在则跳过”,它本质是“查+锁+可能更新”,开销远高于INSERT IGNORE
REPLACE INTO 是锁陷阱,不是安全替代方案
REPLACE INTO 在并发下极易引发死锁,因为它本质是 DELETE + INSERT:先加 X 锁删旧行,再加插入意向锁尝试插新行。两个事务同时操作同一唯一键,就可能一个卡在 DELETE,另一个卡在 INSERT,形成循环等待(ERROR 1213)。
- 自增 ID 跳变:即使最终没插入新行,也会消耗一个 auto-increment 值
- 触发器重复执行、外键级联动作被意外触发
- MVCC 快照可能看到中间状态,主从延迟突增风险高
- 不支持条件更新逻辑(如“仅当新值更大时才更新”),也无法获取每行是“插入”还是“替换”的状态反馈
真正难处理的不是语法选择,而是业务语义和锁行为的错配——比如把“幂等写入”逻辑硬套进 ON DUPLICATE KEY UPDATE,结果锁住整段间隙;或者用 REPLACE INTO 替代 INSERT IGNORE,却没意识到它在 binlog 里留下的是两行事件。这些细节一旦漏掉,线上就是成片的 1205 和 1213。











