删除后插入操作偶现唯一键冲突失败问题排查
解决先删后插导致的唯一键冲突问题
兄弟,这个问题我太熟了——你碰到的是并发场景下的竞态条件导致的唯一键冲突,而且大概率是多个请求同时操作同一个appointment_id时触发的。我给你拆解下原因和解决方案:
问题根源
举个具体场景就能明白:
- 请求A和请求B同时要更新
appointment_id=1001的记录 - 请求A先执行
DELETE,成功删除了这条记录,但还没执行INSERT - 这时候请求B也执行
DELETE,因为A已经删了,所以B的删除也成功 - 接着请求A完成
INSERT,成功写入新记录 - 最后请求B执行
INSERT,就会触发appointment_id的唯一键冲突,操作失败
这种情况在并发量上去之后就会偶尔出现,因为先删后插不是原子操作,中间有时间窗口被其他请求钻空子。
最优解决方案:用原子操作替代先删后插
直接用MySQL的INSERT ... ON DUPLICATE KEY UPDATE语法,把删除+插入的两步操作变成一个原子操作,从根源上避免竞态条件。
示例代码:
INSERT INTO appointment_primary_teams (appointment_id, team_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE team_id = VALUES(team_id);
逻辑说明:
- 如果目标
appointment_id不存在,就直接插入新记录 - 如果目标
appointment_id已经存在,就更新对应的team_id字段 - 整个操作是数据库层面的原子操作,不会被其他请求打断,彻底解决并发冲突
备选方案:加行级锁强制串行化操作
如果业务上必须要先删后插(比如有其他关联逻辑),可以在操作前对目标行加排他锁,确保同一时间只有一个请求能操作这条记录。
示例代码(需要在事务中执行):
-- 开启事务 START TRANSACTION; -- 锁定目标行,其他请求必须等待锁释放才能操作 SELECT appointment_id FROM appointment_primary_teams WHERE appointment_id = ? FOR UPDATE; -- 执行删除和插入 DELETE FROM appointment_primary_teams WHERE appointment_id = ?; INSERT INTO appointment_primary_teams (appointment_id, team_id) VALUES (?, ?); -- 提交事务 COMMIT;
注意事项:
- 必须在事务中执行锁操作,否则锁会立即释放
- 要注意事务的超时时间,避免长时间持有锁导致死锁或者性能问题
- 这种方案会降低并发性能,所以优先推荐第一种原子操作方案
内容的提问来源于stack exchange,提问作者Nicholas Summers




