ON DUPLICATE KEY UPDATE 仅锁定可能触发冲突的唯一键对应行:存在则加X行锁,不存在则加X临键锁;不锁全表,也不锁所有涉及行。

ON DUPLICATE KEY UPDATE 会锁哪些行
它只锁定「可能触发冲突检查」的唯一键对应行,不是全表锁,也不是语句涉及的所有行都锁。核心逻辑是:MySQL 先按 INSERT 的值去查找唯一索引(PRIMARY 或 UNIQUE),找到则加 record lock(行锁),没找到则加 next-key lock(间隙锁 + 行锁)防止幻读插入。哪怕最终走的是 UPDATE 分支,锁也是在 INSERT 查找阶段就加上的。
常见错误现象:Deadlock found when trying to get lock,往往是因为两个事务并发插入相同唯一键值,各自先持有了对同一行的 X 锁,又试图获取对方已持有的锁。
- 如果插入值在唯一索引中已存在 → 加 X record lock 到该行(等同于
SELECT ... FOR UPDATE该行) - 如果插入值在唯一索引中不存在 → 加 X next-key lock 到「该值应插入的位置」(即覆盖间隙 + 虚拟记录)
- 若唯一索引是联合索引,锁范围由匹配的最左前缀决定;未用到的后缀列不影响锁定位
INSERT IGNORE 和 REPLACE INTO 的锁行为差异
INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 在锁类型和范围上基本一致——都是先查再锁,冲突时跳过而非报错。但 REPLACE INTO 是「删 + 插」语义:先 DELETE 匹配行(加 X record lock),再 INSERT 新行(加 X next-key lock)。这意味着它会多一次 delete 操作的锁开销,且可能触发外键级联、触发器、自增 ID 跳变等问题。
使用场景建议:仅当明确需要替换整行(包括非唯一字段)且能接受 delete 语义时才用 REPLACE INTO;日常 upsert 场景优先选 ON DUPLICATE KEY UPDATE。
-
INSERT IGNORE:冲突时静默忽略,不更新,锁行为与ON DUPLICATE KEY UPDATE相同 -
REPLACE INTO:冲突时先 delete 原行(触发 delete 锁 + binlog event),再 insert 新行 - 三者都不支持对多个唯一键冲突做不同处理(比如 A 键冲突 update,B 键冲突 ignore)
唯一索引缺失或失效导致的锁扩大
如果表中没有定义任何 PRIMARY KEY 或 UNIQUE 索引,ON DUPLICATE KEY UPDATE 会直接报错 ERROR 1062 (23000): Duplicate entry '...' for key 'PRIMARY' —— 因为它根本找不到可判断冲突的索引。但更隐蔽的问题是:当唯一索引存在但查询条件未命中索引(比如 WHERE 子句用了函数、类型隐式转换、或前导通配符 like),MySQL 可能无法准确定位冲突行,被迫升级为更宽泛的锁策略,甚至退化为表级意向锁等待。
性能影响明显:本该只锁 1 行的操作,因索引失效而扫描并锁住整个索引范围,极大增加死锁概率和并发阻塞。
- 务必确保
INSERT中用于冲突判断的列(如user_id)上有有效 UNIQUE / PRIMARY 索引 - 避免在 ON DUPLICATE KEY UPDATE 的 INSERT VALUES 中对唯一列做函数操作(如
UPPER(email)) - 用
EXPLAIN FORMAT=TRADITIONAL检查 INSERT 语句是否走了唯一索引
批量 INSERT ... ON DUPLICATE KEY UPDATE 的锁粒度
批量写入时,MySQL 会对每一行独立执行「查找 → 加锁 → 冲突判断 → 更新/插入」流程,不是一次性锁住所有目标行。这意味着:即使你一次插入 1000 行,只要其中某几行的唯一键值相同或落在同一间隙内,它们仍可能相互阻塞或引发死锁。
容易被忽略的一点:批量语句中各行之间的锁顺序取决于 MySQL 内部的索引遍历顺序(通常是 B+ 树升序),而非 SQL 中 VALUES 的书写顺序。因此,并发执行两个结构相同的批量 upsert,若涉及重叠的唯一键值,死锁风险比单条语句更高。
- 批量操作不会合并锁,每行独立加锁;总锁持有时间 ≈ 单行 × 行数(串行化倾向)
- 若批量中含重复唯一键(如两条都插
id=123),第二条会等待第一条释放锁,而非立即失败 - 高并发下建议控制批量大小(如 100 行以内),并确保应用层对唯一键值预先去重
真正难处理的不是锁本身,而是锁的「不可见性」:它不显式出现在语句里,却在唯一索引查找那一刻就已落下。线上遇到慢查询或死锁,别只盯着 UPDATE 部分,先看 INSERT 的值有没有撞上热点唯一键、索引是否真的被用了、批量 size 是否无意中放大了锁竞争。










