重复插入相同数据导致deadlock问题:Deadlock found when trying to get lock; try restarting transaction

8 篇文章 0 订阅
7 篇文章 0 订阅

场景:

业务逻辑:第三方登录情况下,获取到用户的实名信息。之后判断该用户在用户表中是否存在,如果不存在或非实名,那么将其实名;如果已经实名,那么不做处理,直接登录。ORM使用的是spring data jpa,用户表在mobile字段上有唯一索引idx_mobile

发现不定期的发生业务报错:Deadlock found when trying to get lock; try restarting transaction

原因:

分析死锁日志

通过SHOW ENGINE INNODB STATUS;来查看死锁日志:

日志类似

注意:SHOW ENGINE INNODB STATUS\G 看到的DEADLOCK相关信息,只会返回最后的2个事务的信息,而其实有可能有更多的事务才最终导致的死锁

日志的上半部分说明事务1在等待什么锁

ip1 dbuser update

这个用户在执行下面这条sql语句

insert into 用户表 值1

其在申请idx_mobile索引的

RECORD LOCKS space id 3251 page no 14336 n bits 704 index `idx_mobile` of table 用户表 trx id 306872608 lock_mode X locks gap before rec insert intention waiting

这条插入记录的事务等待中,等待获得插入意向锁

 

日志的下半部分说明了事务2当前持有的锁以及等待的锁:

ip2 dbuser update

这个用户在执行下面这条sql语句

insert into 用户表 也是值1

HOLDS THE LOCK(S):

事务2持有S gap lock

lock mode S locks gap before rec

至于为什么加S Gap-Lock ,是因为在插入之前还需要多一步检查:如果记录中有唯一约束,判断存在一条记录等于当前插入的记录时,则需要在这个记录加上S Gap-Lock

也就是说事务1的insert intention lock等待事务2的s gap-lock释放

从日志的WAITING FOR THIS LOCK TO BE GRANTED块中我们可以看到事务2正在申请插入意向锁

那是什么原因造成这个dead lock呢?

事务0的回滚导致事务1和事务2的deadlock

为什么是事务0呢,看后面的参考就知道了,事务1和事务2的死锁是由于事务0rollback导致的

参考:

并发insert操作导致的dead lock

还原整个过程

第三方登录的情况下,前后端没有做重复提交的避免策略,这样会造成一个用户可以多次执行第三方登录的请求,当用户短时间内连续3次(或以上)执行第三方登录的请求,导致会起3个transaction(事务0 事务1 事务2)去执行insert操作

此时如果事务0由于业务代码问题rollback,会导致事务1和事务2 deadlock,直到mysql锁超时,报deadlock错误

即发生:当有3个(或以上)事务对相同的表进行insert操作,如果insert对应的字段上有uniq key约束并且第一个事务rollback了,那其中一个将返回死锁错误信息。

解决方案

避免此DEADLOCK;我们都知道死锁的问题通常都是业务处理的逻辑造成的,既然是uniq key,同时多台不同服务器上的相同程序对其insert一模一样的value,这本身逻辑就不太完美。故解决此问题:

思路1:

保证业务程序别在同一时间点并发的插入相同的值到相同的uniq key的表中

前端可以通过

1.提交数据之前判断当前提交按钮是否存在lock锁

2.在ajax提交之前给提交按钮上锁

3.ajax成功之后或者失败之后解锁

后端可以通过redis+aop来做

参考:redis防表单重复提交

思路2:

由于是事务0 rollback了才产生的deadlock,查明rollback的原因

 

我们的解决方法

我们现在是前端做重复提交的去重。

后端修改了可能产生rollback的逻辑

 

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这个错误通常表示在事务中发生了死锁。死锁是指两个或多个事务互相等待对方所持有的资源,导致无法继续执行。当发生死锁时,数据库会选择其中一个事务作为牺牲品,回滚该事务以解除死锁。 解决这个问题的方法有几种: 1. 重启事务:可以尝试重新启动事务,以便解除死锁。这可能需要在代码中进行适当的异常处理和重试机制。 2. 减少事务范围:如果你的事务涉及到多个资源或查询,可以尝试减少事务的范围,将事务拆分成更小的操作单元。通过减少事务的复杂性,可以减少死锁的概率。 3. 调整事务隔离级别:数据库提供了不同的事务隔离级别,如读未提交、读已提交、可重复读和串行化。你可以尝试调整事务隔离级别来减少死锁的可能性。但需要注意,降低隔离级别可能会导致读取到脏数据或不可重复读的问题,所以需要根据具体情况进行权衡。 4. 优化查询和索引:死锁通常发生在多个事务同时访问相同的资源时。通过优化查询语句和合理使用索引,可以减少对同一资源的竞争,从而降低死锁的概率。 5. 数据库锁定超时:有些数据库管理系统允许配置锁定超时时间,当某个事务持有锁定一段时间后仍未释放时,系统会自动中断该事务以解除死锁。你可以查阅相关文档了解如何配置锁定超时时间。 需要注意的是,死锁是一个复杂的问题,具体的解决方法需要根据你的应用程序和数据库系统进行调整。在调试和解决死锁问题时,建议结合数据库的日志和性能分析工具进行排查。如果问题持续存在,你可能需要进一步详细分析和优化你的数据库设计和事务处理逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值