什么是数据库死锁?
两个或以上事务同时对一批资源占用锁,并形成循环,就会造成事务死锁,一般报错如下:
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来查找,这样直接找主键索引就不会死锁冲突。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐