• 锁竞争导致的慢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);
        ...
        ...
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    调用了更新方法

    @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    由于这里事务里面嵌套了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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

  • 相关阅读:
    宿主机通过wlp3s0接口上网,我应该如何设置
    开源小白到核心开发——我与 sealer 的成长故事
    conda环境安装opencv带cuda版本
    怒刷LeetCode的第12天(Java版)
    ubuntu下使用gcc编译c程序: “error: stray ‘\357’ in program“
    为保护iPhone 13,iPad产量被砍50%,全球芯片短缺带来的影响超乎想象
    netty系列之:来,手把手教你使用netty搭建一个DNS tcp服务器
    Redis-基本介绍/linux下环境配置/配置文件
    grafana对指标进行组合计算
    【数据分享】2022年全国范围的道路数据(全国/分省/分城市)
  • 原文地址:https://blog.csdn.net/sinat_29774479/article/details/127884060