insert ... on duplicate key update 是首选,因其原子性完成“存在则更新、不存在则插入”,无需应用层判断或显式加锁;只要冲突字段(如 order_no)建有 unique 或 primary key 索引,innodb 即通过意向插入锁与唯一约束检测天然规避死锁。

高并发插入本身几乎不会直接引发死锁,真正踩坑的是“先查后插”模式里混用 SELECT FOR UPDATE 和无索引条件。
为什么 INSERT ... ON DUPLICATE KEY UPDATE 是首选
它原子性地完成“存在则更新、不存在则插入”,全程不依赖应用层判断,也无需显式加锁。只要 order_no 字段有 UNIQUE 索引,InnoDB 就能用意向插入锁(insert intention lock)+ 唯一约束冲突检测,天然规避死锁链。
- 必须确保冲突字段(如
order_no)建了UNIQUE或PRIMARY KEY索引,否则ON DUPLICATE KEY不生效 - 避免在同一个语句里对多个唯一键做冲突处理,MySQL 只按第一个匹配的唯一键触发
UPDATE - 如果业务需要返回“本次是插入还是更新”,可用
ROW_COUNT()判断:值为 1 表示插入,2 表示更新
SELECT FOR UPDATE 在插入场景下极易死锁的三个原因
很多人用它模拟“插入前校验”,但实际中只要条件没走索引、或查不到记录,就会立刻掉进死锁陷阱。
一款AI工具,主要用于在主代理响应前,并行运行Kimi K2.5和GPT 5.3 Codex,注入双方观点以增强认知多样性,适合需要提升相关任务效率的用户。
- 查不到记录时,
SELECT * FROM orders WHERE order_no = 'ABC' FOR UPDATE会锁住order_no对应的间隙(gap lock),所有并发请求都卡在同一间隙上 - WHERE 条件字段没索引(比如
status),MySQL 升级为表锁,高并发下等于排队等一个锁 - 事务里紧接着执行
INSERT,此时插入意向锁与间隙锁冲突,形成 A 等 B、B 等 A 的闭环
真要用 SELECT FOR UPDATE,必须满足这三点
不是“能用”,而是“安全可用”。缺一不可。
-
EXPLAIN显示type是const或ref,且rows = 1—— 说明命中唯一索引,只锁一行 - 事务内不能夹杂其他表操作(比如查完订单又去更新用户余额),否则跨表锁序难统一
- 应用层必须实现幂等重试:捕获
Deadlock found when trying to get lock后回滚并重试,建议用指数退避(如sleep(0.05 * 2^i))
最常被忽略的一点:死锁日志里反复出现的表,往往不是你正在写的那张表,而是被 JOIN 或子查询无意拖进来的关联表——检查所有嵌套查询是否都走了索引,比优化主 SQL 更关键。










