前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >记一次线上问题 → Deadlock 的分析与优化

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

作者头像
青石路
发布2023-10-16 16:27:24
980
发布2023-10-16 16:27:24
举报
文章被收录于专栏:开发技术开发技术

问题复现

  需求背景

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

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

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

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

  线上问题

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

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

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

  是不是无比的契合需求?

  但是,真的就完美无瑕吗

  且看我表演一波

  表演代码如下:

代码语言:javascript
复制
@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("主线程执行完毕");
}

Deadlock 就这么诞生了!

优化处理

  死锁产生条件

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

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

  死锁自然就产生了

  那么该如何处理了

  排序处理

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

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

  分批处理

  事务时间越短越好

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

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

  加锁处理

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

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

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

总结

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

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

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

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

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

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

    有的业务对性能要求高

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-07-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题复现
    •   需求背景
      •   线上问题
      • 优化处理
        •   死锁产生条件
          •   排序处理
            •   分批处理
              •   加锁处理
              • 总结
              相关产品与服务
              数据库
              云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档