什么是数据库死锁?



两个或以上事务同时对一批资源占用锁,并形成循环,就会造成事务死锁,一般报错如下:



com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction


1 插入场景:(user_id和pool_id是联合唯一索引)



场景:有一个job,定时会查出一批数据,然后分页,每页一千条数据批量插入数据库中,伪代码如下:



---------------------
 
 
int totalRow = xxxMapper.count();
 
 
for (int page=0; page <= totalRow/pageSize; page++) {
 
 
    List<Xxx> data = xxxMapper.selectByPage(page * pageSize, pageSize);  // 这里分页可用上一次的最大id,用偏移量只是方便理解
 
 
    if (CollectionUtils.isNotEmpty(data)) xxxMapper.batchInsert(data);
 
 
}


---------------------



上面没有开一个统一的事务,所以每次batchInsert()都是一次独立新的事务,多次独立事务批量插入,并且user_id和pool_id是联合唯一索引当时就发现如下问题:


### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
 
 
### SQL: insert into leads_distribution_pool ( user_id, pool_id ) values ( ?,         ? ) ,  ( ?,         ? );



- 产生原因分析(IK等待NK,而不同事务之间的NK又会相互等待RK导致的):



在分批插入中,事务A会先申请对应行记录(假设是1,2,3)的行锁X RK(排他 行级锁),然后事务B也会申请对应行记录(假设是4,5,6)X RK. 这时候事务A他需要插入,会先申请一个X IK(排他 插入意向锁),因为X IK是需要获取S NK锁(共享 next-key锁),但是由于事务B对4,5,6加了X RK,所以事务A的next-key锁里面的间隙锁无法对1,2,3周围的间隙如4加一个锁,这时候事务A就获取S NK锁失败,导致无法获取X IK锁,同理事务B也是这种,由于事务A的X NK阻塞,导致获取插入意向锁也失败,这样就会形成一种相互等待的状态,从而导致死锁。



- 解决方案:


-- 第一种:给批量插入加一个redis锁,处理完一个在处理下一个批量插入
 
 
-- 第二种:降低隔离级别,比如把隔离级别变为可提交读(Innodb中S NK和X IK是Innodb可重复度级别才有)

 



2 更新场景:(teacher_oa_id是普通索引,id是主键索引)


- 原来的SQL:update user set hujin_wx_id = 121 WHERE teacher_oa_id = #{oaId}
 
 
- 改造后的SQL:update user set hujin_wx_id = 121 WHERE id = #{id}


区别就是原来的用了两个索引,而innodb中的行级锁是锁索引的,先会锁住非主键索引,再根据非主键索引找到主键索引,然后把主键索引锁住。



问题就在这里,假设有另一个sql是:update user set teacher_oa_id = #{oaId}  WHERE id = #{id},这样就先锁住主键索引,然后更新teacher_oa_id需要获取这个非主键索引的行级锁,然后这时候由于另一条sql已经锁住teacher_oa_id索引了,这时候这条sql想去锁teacher_oa_id索引就失败了。然后原来的sql等待id主键索引的锁,另一个sql等待teacher_oa_id的索引.解决办法:将oaId改成用id来查找,这样直接找主键索引就不会死锁冲突。




数据库死锁产生原因及场景_java