MySQL5.7并发INSERT ON DUPLICATE KEY UPDATE导致死锁

本文探讨了在MySQL5.7中,多个线程并发执行INSERT ON DUPLICATE KEY UPDATE操作时可能出现的死锁问题。通过死锁场景复现、分析,发现事务因插入意向锁和唯一索引的gap lock冲突导致死锁。解决方案包括降低事务隔离级别、避免使用unique索引或回退到MySQL5.6版本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

MySQL5.7版本多个线程并发往同一个数据间隙INSERT …… ON DUPLICATE KEY UPDATE,执行的是insert时产生死锁

死锁场景复现

环境: mysql5.7,事务隔离级别REPEATABLE-READ

表结构:

CREATE TABLE `a1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uid` (`uid`)
) ENGINE=InnoDB

数据:
mysql> select * from a1;
+----+-----+
| id | uid |
+----+-----+
|  1 |   1 |
| 10 |  10 |
| 15 |  15 |
+----+-----+

并发事务:

T1T2T3
begin;
insert into a1(id, uid) values(11, 11) on duplicate key update id = values(id),uid = values(uid);
Query OK, 1 row affected (0.00 sec)
begin;
insert into a1(id, uid) values(12, 12) on duplicate key update id = values(id),uid = values(uid);// wating,被阻塞
begin;
insert into a1(id, uid) values(13, 13) on duplicate key update id = values(id),uid = values(uid);// wating,被阻塞
rollback;
OK, 1 row affectedERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

在事务1回滚后,事务2、3发生死锁

死锁分析

事务1执行SQL,对主键索引加插入意向锁,同时也对uid唯一索引加gap lock,范围为(10,15),以及唯一索引上的插入意向锁,而事务2、3在执行insert时,由于插入意向锁与gap lock冲突,所以事务2、3都被阻塞,但是又由于gap lock之间是兼容的,实际上事务2、3都获取到了唯一索引上的gap lock,当事务1回滚时,事务2、3相互等待对方的gap lock,造成死锁。

加锁分析

当表中既有主键又有唯一键时,当执行INSERT …… ON DUPLICATE KEY UPDATE最终执行的是insert 时,在主键索引树添加插入意向锁,同时也在 unique index tree 添加GAP lock和插入意向锁,

死锁解决

  • 隔离级别改用RC
  • 删除唯一索引,尽量减少在数据库中使用unique index,因为unique key 会引起额外的index检查,需要更大的开销,本例中duplicate-key checking会使用gap lock
  • mysql版本改为5.6,mysql5.6是没有这个问题的

参考

https://zhuanlan.zhihu.com/p/29349080

https://juejin.im/entry/5adca48df265da0b9c1037c8

关于为什么要在唯一索引加gap lock,可参考上述链接

转载于:https://my.oschina.net/hebaodan/blog/3045246

### 解决方案概述 在MySQL 5.7中遇到`ON DUPLICATE KEY UPDATE`语句报错的情况,通常涉及死锁、约束违反或其他数据库异常。为了有效处理这些问题并找到合适的解决方案,可以从以下几个方面入手: #### 死锁预防与解决措施 当多个线程尝试在同一时间向具有相同唯一键的数据间隙插入新记录时可能发生死锁现象[^1]。为了避免这种情况的发生,可以考虑调整应用程序逻辑以减少并发度或优化表结构设计来降低锁定竞争的可能性。 对于已经发生的死锁错误(如Error Code: 1213),建议采取重试机制,在捕获到此类特定类型的错误之后自动重新提交受影响的操作直到成功为止。此外还可以通过设置较低级别的隔离级别(READ COMMITTED)从而减小产生gap locks的概率进而缓解潜在的死锁风险[^5]。 #### 处理重复键冲突 如果是因为存在重复的关键字而导致无法正常执行,则应确保所使用的SQL语句正确指定了如何应对这种情形下的行为。例如利用`ON DUPLICATE KEY UPDATE`特性可以在检测到主键或唯一索引冲突时更新已有的行而非抛出错误消息[^3]。 ```sql INSERT INTO table_name (id, name) VALUES (1,'Alice') ON DUPLICATE KEY UPDATE name=VALUES(name); ``` 上述代码片段展示了怎样安全地处理可能存在的重复ID情况——即使目标记录已经被占用也会相应修改其name字段的内容而不至于失败退出。 #### 验证数据完整性 有时问题根源在于试图插入不符合预期模式的数据集内;因此有必要事先验证待写入的信息是否满足既定条件限制。比如检查外键关联的有效性、字符长度合规与否等等因素均有可能触发相应的警告提示甚至致命性的中断事件。 另外值得注意的一点是在某些特殊情况下可能会因为隐含转换规则引起意外的结果,故而在编写具体实现之前最好先查阅官方文档确认各类型之间的映射关系以便做出恰当的选择[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值