一、背景
在测试环境测试给用户并发发送卡券时,出现了死锁,但看代码没有死锁,问题如下图
看日志确实发生了死锁,按照死锁产生的原因:一般死锁是两把锁两个人争抢,每个人都获得其中一把,谁都不让谁,等待对方释放锁,死循环导致的,图示如下
二、问题点
1. ### SQL: select * from score_user where user_id = ? for update,这个sql查询是发送了死锁
三、排查过程
1. 根据经验和死锁产生的条件,猜测代码并发执行,一个线程先锁住了表A的记录,另外一个线程由于原因没有线索表A记录,而锁住了表B的记录,接下来,锁住A记录的线程等待B的锁是否,锁住B的线程等待A的锁释放,所以产生了原因,所以先看代码 下载
2. 代码如下面所示,可以看到,基本逻辑,都是先插入score_gain_stream
3. 看代码,不会发生死锁的,多个线程同时在执行,每个线程都开启事务,每个线程都加锁查询score_user,发现都没有查询到,那么每个线程都执行插入score_gain_stream操作,都成功,接下来,进行插入score_user,这里面只有一个线程可以成功下载,有唯一主键,其他线程这里会报错,接下来代码抓取异常,进行加锁查询,此时报错,死锁了
在测试环境测试给用户并发发送卡券时,出现了死锁,但看代码没有死锁,问题如下图

看日志确实发生了死锁,按照死锁产生的原因:一般死锁是两把锁两个人争抢,每个人都获得其中一把,谁都不让谁,等待对方释放锁,死循环导致的,图示如下

二、问题点
1. ### SQL: select * from score_user where user_id = ? for update,这个sql查询是发送了死锁
三、排查过程
1. 根据经验和死锁产生的条件,猜测代码并发执行,一个线程先锁住了表A的记录,另外一个线程由于原因没有线索表A记录,而锁住了表B的记录,接下来,锁住A记录的线程等待B的锁是否,锁住B的线程等待A的锁释放,所以产生了原因,所以先看代码 下载
2. 代码如下面所示,可以看到,基本逻辑,都是先插入score_gain_stream
- @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,rollbackFor = Exception.class)
- public boolean generateScoreInfo(String userId, Integer score,
- Long scoreRuleId, int scoreType, int scoreStatus, String scoreWay,
- String orderId, String inviteeId, String reqId, Integer eventVersion) {
- //0:参数判断
- if(null == score || score <= 0) {
- log.warn("score null or < 0, userId:" + userId + "scoreRuleId:" + scoreRuleId + ",scoreWay:" + scoreWay);
- return true;
- }
- //1:获取用户等级
- int memberLevel = MemberLevel.GENERAL_MEMBER;
- ScoreUser dbScoreUser = scoreUserManager.getScoreUserByUserIdForUpdate(userId);
- boolean isCreate = null == dbScoreUser ? true : false;
- if (!isCreate) {
- memberLevel = dbScoreUser.getMemberLevel();
- }
- // 2:构造/生成积分流水
- ScoreGainStream scoreGainStream = contructSocreGainStream(userId, score, scoreRuleId, scoreType, scoreStatus, scoreWay,
- orderId, inviteeId, reqId, eventVersion,memberLevel);
- boolean streamFlag = addScoreGainStream(scoreGainStream);
- if(!streamFlag){
- log.error("addScoreGainStream error,data:" + scoreGainStream.toString());
- return false;
- }
- // 3:判断用户类型
- if(isCreate){//新增积分用户信息
- try { 下载
- boolean addFlag = addScoreUser(userId, memberLevel, scoreType, score);
- if(!addFlag){
- log.error("generateScoreInfo addScoreUser error, userId:" + userId + "|" + "score:" + score );
- throw new RuntimeException("generateScoreInfo addScoreUser error");
- }
- } catch (Exception e) {
- if(e instanceof DuplicateKeyException){
- log.warn("addScoreUser DuplicateKeyException,userId:" + userId + "|" + "score:" + score);
- //查询用户信息
- ScoreUser updateUser = contructUpdateScoreUser(scoreUserManager.getScoreUserByUserIdForUpdate(userId), score, scoreStatus);
- boolean flag = scoreUserManager.updateUserScoreInfoById(updateUser) > 0 ? true : false;
- if(!flag){
- log.error("generateScoreInfo updateUserScoreInfoById error, data:" + updateUser.toString());
- throw new RuntimeException("generateScoreInfo updateUserScoreInfoById error");
- }
- return true;
- }else{
- log.error("addScoreUser error,userId:" + userId + "|" + "score:" + score, e);
- return false;
- }
- }
- return true;
- }else{//更新积分用户信息
- ScoreUser updateScoreUser = contructUpdateScoreUser(dbScoreUser, score, scoreStatus);
- boolean flag = scoreUserManager.updateUserScoreInfoById(updateScoreUser) > 0 ? true : false;
- if(!flag){
- log.error("generateScoreInfo updateUserScoreInfoById error, data:" + updateScoreUser.toString());
- throw new RuntimeException("generateScoreInfo updateUserScoreInfoById error");
- }
- return true;
- }
- }
3. 看代码,不会发生死锁的,多个线程同时在执行,每个线程都开启事务,每个线程都加锁查询score_user,发现都没有查询到,那么每个线程都执行插入score_gain_stream操作,都成功,接下来,进行插入score_user,这里面只有一个线程可以成功下载,有唯一主键,其他线程这里会报错,接下来代码抓取异常,进行加锁查询,此时报错,死锁了