本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Java锁竞争导致sql慢日志原因分析
itnanba · 132浏览 · 发布于2022-11-21 +关注

这篇文章主要介绍了Java锁竞争导致sql慢的日志原因分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧


线上在同步用户时,经常出现简单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;
}

声明式事务使用很简单,可以自动帮我们进行事务的开启、提交以及回滚等操作,但是事务的颗粒度是整个方法,无法进行精细化控制。在使用过程中要注意事务的范围与其他中间件的交互,通过指定适当的传播等级来达到效果。


相关推荐

RN开发环境的npm私库本地debug调试

manongba · 698浏览 · 2019-05-09 17:03:46
你不知道的浏览器渲染原理

追忆似水年华 · 1372浏览 · 2019-05-09 22:47:56
基于iview的router常用控制方式

追忆似水年华 · 998浏览 · 2019-06-03 10:39:21
编程小知识之 JavaScript 文件读取

manongba · 717浏览 · 2019-06-10 09:16:16
10个省时间的 PyCharm 技巧 赶快收藏!

· 702浏览 · 2019-06-10 09:32:01
加载中

0评论

评论
分类专栏
小鸟云服务器
扫码进入手机网页