经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
记一次线上问题 → Deadlock 的分析与优化
来源:cnblogs  作者:青石路  时间:2023/7/31 9:21:37  对本文有异议

开心一刻

  今天女朋友很生气

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

  我:这话怎么说

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

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

  女朋友:啥事呀?

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

问题复现

  需求背景

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

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

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

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

  线上问题

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

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

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

  是不是无比的契合需求?

  但是,真的就完美无瑕吗

  且看我表演一波

  表演代码如下:

  1. @Override
  2. @Transactional(rollbackFor = Exception.class)
  3. public void batchSaveOrUpdate(List<TblClassStudent> classStudents) {
  4. if(CollectionUtils.isEmpty(classStudents)) {
  5. return;
  6. }
  7. classStudents.forEach(classStudent -> {
  8. this.getBaseMapper().saveOrUpdate(classStudent);
  9. try {
  10. // 为了方便复现问题,睡眠1秒
  11. TimeUnit.SECONDS.sleep(1);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. });
  16. }
  17. // 单元测试
  18. @Test
  19. public void batchSaveOrUpdateTest() throws InterruptedException {
  20. TblClassStudent classStudent = new TblClassStudent();
  21. classStudent.setId(1);
  22. classStudent.setClassNo("20231010");
  23. classStudent.setStudentNo("20231010201");
  24. TblClassStudent classStudent1 = new TblClassStudent();
  25. classStudent1.setId(2);
  26. classStudent1.setClassNo("20231010");
  27. classStudent1.setStudentNo("20231010202");
  28. List<TblClassStudent> classStudents1 = new ArrayList<>();
  29. classStudents1.add(classStudent);
  30. classStudents1.add(classStudent1);
  31. List<TblClassStudent> classStudents2 = new ArrayList<>();
  32. classStudents2.add(classStudent1);
  33. classStudents2.add(classStudent);
  34. // 模拟2个线程,同时批量更新
  35. CountDownLatch latch = new CountDownLatch(2);
  36. new Thread(() -> {
  37. studentService.batchSaveOrUpdate(classStudents1);
  38. latch.countDown();
  39. }, "t1").start();
  40. new Thread(() -> {
  41. studentService.batchSaveOrUpdate(classStudents2);
  42. latch.countDown();
  43. }, "t2").start();
  44. latch.await();
  45. System.out.println("主线程执行完毕");
  46. }
View Code

   Deadlock 就这么诞生了!

优化处理

  死锁产生条件

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

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

  死锁自然就产生了

  那么该如何处理了

  排序处理

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

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

  分批处理

  事务时间越短越好

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

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

  加锁处理

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

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

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

总结

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

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

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

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

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

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

    有的业务对性能要求高

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

    

原文链接:https://www.cnblogs.com/youzhibing/p/17578206.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号