Java锁竞争导致sql慢日志原因分析
古柏树下 人气:2线上在同步用户时,经常出现简单sql的慢日志。根据方法找到代码,发现方法内使用redisson进行锁操作,waiTime和leaseTime都为3秒,数据库操作比较简单,只是一个简单的用户更新操作。代码简化后如下
@Override @Transactional(rollbackFor = Exception.class) public LiveMemberResponseDTO becomeStudentNotQueryOrder(LiveBecomeStudentForOrderRequestDTO requestDTO) { EduUserEntity user = eduUserService.syncLocalByAccount(requestDTO.getOrderAccountId(), UserRegisterChannel.LIVE_APPLY); EduUserEntity applyUser = eduUserService.syncLocalByAccount(requestDTO.getApplyAccountId(), UserRegisterChannel.LIVE_APPLY); ... ... ... }
调用了更新方法
@Override @Lock(waitTime = 10000, leaseTime = 3000, value = RedisConstant.USER_SYNC_LOCAL, key = "#accountId") public EduUserEntity syncLocalByAccount(String accountId, String mobile, String fullName, String source, UserRegisterChannel registerChannel) { EduUserEntity eduUserEntity = queryUsersByAccountId(accountId); boolean updateFlag = false; .... .... ....//判断是否需要更新 if (updateFlag) { saveOrUpdate(eduUserEntity); } return eduUserEntity; }
由于这里事务里面嵌套了redis锁,并且涉及到更新表,可能会有死锁的情况。通过在获取锁的地方打上地址获取到以下日志
可以看到[Thread-13]在等待了三秒后才获取到redis,根据获取锁的时机,列出表格
事务A(Thread-13) | 事务B(Thread-14) |
---|---|
获取redis锁 | |
获取db锁 | |
释放redis锁 | |
获取redis锁 | |
尝试获取db锁 | |
尝试获取redis锁 | |
等待三秒 | |
redis锁超时,释放redis锁 | |
成功获取redis锁 | |
完成业务 | |
释放redis和db锁 | |
获取db锁 |
这是由于两次更新user表过程中,使用了一个事务A,导致事务B来获取db行锁的时候,被事务A阻塞。但在被事务A阻塞前,已经获取到了redis锁,所以导致事务A在获取第二次更新的redis锁的时候被阻塞,造成了死锁。
最终导致,redis锁超时,日志方面体现为慢sql,因为事务B的sql等待了三秒才拿到锁。
这种情况是因为事务和redis锁的嵌套导致,所以指定更新方法的事务传播等级为PROPAGATION_REQUIRED_NEW,不管外层是否存着事务,都开启新事务。
修改后代码
@Override @Lock(waitTime = 10000, leaseTime = 3000, value = RedisConstant.USER_SYNC_LOCAL, key = "#accountId") //这里开启新事务 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public EduUserEntity syncLocalByAccount(String accountId, String mobile, String fullName, String source, UserRegisterChannel registerChannel) { EduUserEntity eduUserEntity = queryUsersByAccountId(accountId); boolean updateFlag = false; .... .... ....//判断是否需要更新 if (updateFlag) { saveOrUpdate(eduUserEntity); } return eduUserEntity; }
声明式事务使用很简单,可以自动帮我们进行事务的开启、提交以及回滚等操作,但是事务的颗粒度是整个方法,无法进行精细化控制。在使用过程中要注意事务的范围与其他中间件的交互,通过指定适当的传播等级来达到效果。
加载全部内容