记一次线上问题 → Deadlock 的分析与优化

一次,问题,deadlock,分析,优化 · 浏览次数 : 1202

小编点评

**需求是否很明确?** 是的,要求很明确。代码说明了逐行更新,存在则更新,不存在则插入的逻辑,并且提供了示例代码以说明每个步骤的执行过程。 **是不是无比的契合需求?** 部分细节的处理,例如睡眠1秒的延迟,可能会影响效率,需要根据具体需求进行调整。 **代码是否完美无瑕?** 代码基本完成的功能描述,但一些细节的处理,例如睡眠1秒的延迟,可能影响效率,需要根据具体需求进行调整。 **优化处理死锁的原因** * 由于是数据库层面的事务,在多个线程更新同一个记录时,会导致死锁。 * 由于代码没有进行锁的释放,多个线程可能会争夺同一个数据库连接。 * 由于没有对事务的隔离,多个线程可能会并发更新同一个记录。 **优化处理死锁的方法** * 使用分批处理或加锁处理。 * 使用不同的数据库连接或事务隔离级别。 * 在执行更新操作之前释放数据库连接或事务。 * 避免使用睡眠或等待操作等阻塞性的操作。

正文

开心一刻

  今天女朋友很生气

  女朋友:我发现你们男的,都挺单纯的

  我:这话怎么说

  女朋友:脑袋里就只想三件事,搞钱,跟谁喝点,还有这娘们真好看

  我:你错了,其实我们男人吧,每天只合计一件事

  女朋友:啥事呀?

  我:这娘们真好看,得搞钱跟她喝点

问题复现

  需求背景

   MySQL8.0.30 ,隔离级别是默认的,也就是 REPEATABLE-READ 

  表: tbl_class_student ,id 非自增,整张表的全部字段数据都是从上游服务进行同步

  需求:上游服务发送同步MQ,本服务收到消息后再调上游服务接口,查询全量数据,对 tbl_class_student 表数据进行更新,若记录存在则更新,不存在则插入

  这需求是不是很明确?放心,没有下套!

  线上问题

  通过线上异常日志,最终定位到如下代码

  咋一看,这代码是不是无比的清晰明了?

  都不用注释,就能清楚的知道这个代码是在做什么:逐行更新,存在则更新,不存在则插入

  是不是无比的契合需求?

  但是,真的就完美无瑕吗

  且看我表演一波

  表演代码如下:

@Override
@Transactional(rollbackFor = Exception.class)
public void batchSaveOrUpdate(List<TblClassStudent> classStudents) {
    if(CollectionUtils.isEmpty(classStudents)) {
        return;
    }
    classStudents.forEach(classStudent -> {
        this.getBaseMapper().saveOrUpdate(classStudent);
        try {
            // 为了方便复现问题,睡眠1秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}

// 单元测试
@Test
public void batchSaveOrUpdateTest() throws InterruptedException {

    TblClassStudent classStudent = new TblClassStudent();
    classStudent.setId(1);
    classStudent.setClassNo("20231010");
    classStudent.setStudentNo("20231010201");

    TblClassStudent classStudent1 = new TblClassStudent();
    classStudent1.setId(2);
    classStudent1.setClassNo("20231010");
    classStudent1.setStudentNo("20231010202");

    List<TblClassStudent> classStudents1 = new ArrayList<>();
    classStudents1.add(classStudent);
    classStudents1.add(classStudent1);

    List<TblClassStudent> classStudents2 = new ArrayList<>();
    classStudents2.add(classStudent1);
    classStudents2.add(classStudent);

    // 模拟2个线程,同时批量更新
    CountDownLatch latch = new CountDownLatch(2);
    new Thread(() -> {
        studentService.batchSaveOrUpdate(classStudents1);
        latch.countDown();
    }, "t1").start();
    new Thread(() -> {
        studentService.batchSaveOrUpdate(classStudents2);
        latch.countDown();
    }, "t2").start();
    latch.await();
    System.out.println("主线程执行完毕");
}
View Code

   Deadlock 就这么诞生了!

优化处理

  死锁产生条件

  死锁产生的条件,大家还记得吗?

  回到上诉案例,锁的持有、申请情况如下

  死锁自然就产生了

  那么该如何处理了

  排序处理

  不同线程调用同一个方法处理数据而产生死锁

  这种情况对处理的数据进行排序处理,使得不同线程申请数据库锁的顺序保持一致,那么就不会产生死锁

  分批处理

  事务时间越短越好

  批量逐条更新,会导致事务持续的时间很长,那么出现死锁的概率就越大

  分批处理可以减少事务时长

  加锁处理

  这里的锁指的并非数据库层面的锁,而是业务代码层面的锁

  可以是 JVM 的锁,适用于单节点部署的情况

  可以是分布式锁,适用于单节点部署,也适用于多节点部署;具体实现方式有很多,结合实际情况选择一种合适的实现方式即可

总结

  1、批量逐条更新,这是严令禁止的

    效率低下,导致事务时长大大增加,会引发一系列其他的问题

  2、数据库的加锁是比较复杂的,不同的数据库的加锁实现也是有区别的

    本篇中的死锁案例还是比较好分析的

    遇到不好分析的,需要向同事(dba、开发同事等)发出求助,也可以线上求助数据库博主

  3、面对不同问题,结合业务来分析出最合适的处理方式

    有的业务对性能要求高

    有的业务对数据准确性要求高

    

与记一次线上问题 → Deadlock 的分析与优化相似的内容:

记一次线上问题 → Deadlock 的分析与优化

开心一刻 今天女朋友很生气 女朋友:我发现你们男的,都挺单纯的 我:这话怎么说 女朋友:脑袋里就只想三件事,搞钱,跟谁喝点,还有这娘们真好看 我:你错了,其实我们男人吧,每天只合计一件事 女朋友:啥事呀? 我:这娘们真好看,得搞钱跟她喝点 问题复现 需求背景 MySQL8.0.30 ,隔离级别是默认

记一次 Redisson 线上问题 → ERR unknown command 'WAIT' 的排查与分析

开心一刻 昨晚和一个朋友聊天 我:处对象吗,咱俩试试? 朋友:我有对象 我:我不信,有对象不公开? 朋友:不好公开,我当的小三 问题背景 程序在生产环境稳定的跑着 直到有一天,公司执行组件漏洞扫描,有漏洞的 jar 要进行升级修复 然后我就按着扫描报告将有漏洞的 jar 修复到指定的版本 自己在开发

记一次线上Redis内存占用过高、大Key问题的排查

问题背景 在一个风和日丽的下午,公司某项目现场运维同学反馈,生产环境3个Redis的Sentinel集群节点内存占用都很高,达到了17GB的内存占用量。 稍加思索,应该是某些Key的Value数据体量过大,占用了过多的内存空间,我们在使用Redis的过程中,单个Value或者单个集合中的元素应该保证

[转帖]记一次线上Oracle连接耗时过长的问题

https://www.cnblogs.com/changxy-codest/p/15670495.html 问题现象 1、远程Oracle数据库通过IP:PORT/SERVICE_NAME连接 2、应用服务通过Docker容器部署,访问Oracle联通性测试接口,需要50s左右才能返回连接成功;

记一次618军演压测TPS上不去排查及优化

本文内容主要介绍,618医药供应链质量组一次军演压测发现的问题及排查优化过程。旨在给大家借鉴参考。

记一次栈溢出异常问题的排查

刚修改的服务,推到开发环境之后,总是时不时的崩溃,但是不知道为什么。尝试找到他的最后一次调用,也没有复现。 没有办法,只能抓dump了。 开启崩溃自动dump,网络上很多,不赘述了。 拿到dump之后,首先看看是什么类型的异常 如图所示,是个栈溢出的异常。 打印一下堆栈,发现密密麻麻的全是这个代码。

记一次 .NET某家装ERP系统 内存暴涨分析

一:背景 1. 讲故事 前段时间微信上有一位老朋友找到我,说他的程序跑着跑着内存会突然爆高,有时候会下去,有什么会下不去,怀疑是不是某些情况下存在内存泄露,让我帮忙分析一下,其实内存泄露方面的问题还是比较好解决的,看过这个dump之后觉得还是有一定的分享价值,拿出来和大家分享一下吧。 二:WinDb

记一次 Visual Studio 2022 卡死分析

一:背景 1. 讲故事 最近不知道咋了,各种程序有问题都寻上我了,你说 .NET 程序有问题找我能理解,Windows 崩溃找我,我也可以试试看,毕竟对 Windows 内核也知道一丢丢,那 Visual Studio 有问题找我就说不过去了,但又不好拒绝,就让朋友发下卡死的 dump 我看一看。

记一次nginx配置不当引发的499与failover 机制失效

背景 nginx 499在服务端推送流量高峰期长期以来都是存在的,间或还能达到告警阈值触发一小波告警,但主观上一直认为499是客户端主动断开,可能和推送高峰期的用户打开推送后很快杀死app有关,没有进一步探究问题根源。 然而近期在非高峰期也存在499超过告警阈值的偶发情况,多的时候一天几次,少的时候

记一次 .NET 某车零件MES系统 登录异常分析

一:背景 1. 讲故事 这个案例有点特殊,以前dump分析都是和软件工程师打交道,这次和非业内人士交流,隔行如隔山,从指导dump怎么抓到问题解决,需要一个强大的耐心。 前几天有位朋友在微信上找到我,说他们公司采购的MES系统登录的时候出现了异常,让我帮忙看一下,我在想解铃还须系铃人,怎么的也不应该