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

场景:

业务逻辑:第三方登录情况下,获取到用户的实名信息。之后判断该用户在用户表中是否存在,如果不存在或非实名,那么将其实名;如果已经实名,那么不做处理,直接登录。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的逻辑

 

发布了442 篇原创文章 · 获赞 222 · 访问量 115万+

猜你喜欢

转载自blog.csdn.net/u013905744/article/details/102897226