开心一刻

  今天女朋友很生气

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

  我:这话怎么说

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

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

  女朋友:啥事呀?

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

问题复现

  需求背景

   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("主线程执行完毕");
}

   Deadlock 就这么诞生了!

优化处理

  死锁产生条件

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

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

  死锁自然就产生了

  那么该如何处理了

  排序处理

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

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

  分批处理

  事务时间越短越好

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

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

  加锁处理

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

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

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

总结

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

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

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

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

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

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

    有的业务对性能要求高

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

    

记一次线上问题 → Deadlock 的分析与优化的更多相关文章

  1. 记一次线上bug排查-quartz线程调度相关

    记一次线上bug排查,与各位共同探讨. 概述:使用quartz做的定时任务,正式生产环境有个任务延迟了1小时之久才触发.在这一小时里各种排查找不出问题,直到延迟时间结束了,该任务才珊珊触发.原因主要就 ...

  2. 解Bug之路-记一次线上请求偶尔变慢的排查

    解Bug之路-记一次线上请求偶尔变慢的排查 前言 最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章. Bug现场 这是一个偶发的性能问题.在每天几百万比交易请求中,平均 ...

  3. 记一次线上MySQL数据库死锁问题

            最近线上项目报了一个MySQL死锁(DealLock)错误,虽说对业务上是没有什么影响的,由于自己对数据库锁这块了解不是很多,之前也没怎么的在线上碰到过.这次刚好遇到了,便在此记录一下 ...

  4. 记一次线上Curator使用过程JVM栈溢出解决

       为了同学们看起来一目了,特按如下思路进行讲解. 1.出现的场景    2.分析及解决的过程    3.总结 最近公司要使用zookeeper做配置管理(后面简称ZK),然后自己就提前用虚拟机进行 ...

  5. 记一次线上coredump事故

    1.事故背景 上周三凌晨,我负责的某个模块在多台机器上连续发生coredump,幸好发生在业务低峰期,而且该模块提供的功能也不是核心流程功能,所以对线上业务影响比较小.发生coredump后,运维收到 ...

  6. 记一次线上事故的JVM内存学习

    今天线上的hadoop集群崩溃了,现象是namenode一直在GC,长时间无法正常服务.最后运维大神各种倒腾内存,GC稳定后,服务正常.虽说全程在打酱油,但是也跟着学习不少的东西. 第一个问题:为什么 ...

  7. 记一次线上gc调优的过程

           近期公司运营同学经常表示线上我们一个后台管理系统运行特别慢,而且经常出现504超时的情况.对于这种情况我们本能的认为可能是代码有性能问题,可能有死循环或者是数据库调用次数过多导致接口运行 ...

  8. 记一次线上Kafka消息堆积踩坑总结

    2018年05月31日 13:26:59 xiaoguozi0218 阅读数:2018更多 个人分类: 大数据   年后上线的系统,与其他业务系统的通信方式采用了第三代消息系统中间件Kafka.由于是 ...

  9. 记一次线上由nginx upstream keepalive与http协议"协作"引起的接口报错率飙高事件

    年前接到个任务,说要解决线上一些手机客户端接口报错率很高的问题.拿到了监控邮件,粗略一看,各种50%+的错误率,简直触目惊心.这种疑难杂症解决起来还是挺好玩的,于是撸起袖子action. 最终的结果虽 ...

  10. 记一次线上dubbo服务超时和线程池满问题排查

    线上某dubbo服务A调用dubbo服务B的接口X方法,调用端A日志中出现了很多超时的情况,提供端B该接口X超时时间设置为60s: 查看提供端B的日志,报了很多线程池满的异常: Caused by: ...

随机推荐

  1. MySQL对时间戳的格式化

    时间转时间戳 select unix_timestamp('2022-02-22 22:22:22'); 时间戳转时间 select from_unixtime(1645539742); 格式化 SE ...

  2. 认识CPU底层原理(1)——MOSFET

    本文为B站UP主硬件茶谈制作的系列科普<[硬件科普]带你认识CPU>系列的学习笔记,仅作个人学习记录使用,如有侵权,请联系博主删除 近年来,由于国内外各种因素影响,半导体行业逐渐被推向风口 ...

  3. vue全家桶进阶之路35:Vue3 传递参数query和params

    在 Vue.js 3.x 中,可以通过路由的 params 和 query 属性来传递参数. 通过 params 传递参数 我们可以在路由跳转时通过 params 传递参数.具体方法如下: // 在组 ...

  4. 最详细的Git命令大全

    Git常用命令及方法大全 下面是我整理的常用 Git 命令清单.几个专用名词的译名如下. Workspace:工作区 Index / Stage:暂存区 Repository:仓库区(或本地仓库) R ...

  5. Java(instanceof和类型转换)

    1.instanceof和类型转换 instanceof 引用类型比较,判断一个对象是什么类型 public static void main(String[] args) { // Object & ...

  6. ASP.NET Core 6框架揭秘实例演示[37]:重定向的N种实现方式

    在HTTP的语义中,重定向一般指的是服务端通过返回一个状态码为3XX的响应促使客户端像另一个地址再次发起请求,本章将此称为"客户端重定向".既然有客户端重定向,自然就有服务端重定向 ...

  7. Hive执行计划之只有map阶段SQL性能分析和解读

    目录 目录 概述 1.不带函数操作的select-from-where型简单SQL 1.1执行示例 1.2 运行逻辑分析 1.3 伪代码解释 2.带普通函数和运行操作符的普通型SQL执行计划解读 2. ...

  8. String和new String的那点事

    String a= "test"; 此语句含义是:在常量池中创建test字符串对象,变量aa是对常量池中此对象的引用 String aa = new String("te ...

  9. ChatGPT的ABAP能力如何?

    ChatGPT是最近的热门话题,作为语言模型,它擅长处理各种语言相关的问题.显然,ABAP也是一种语言,ABAP开发者的很大一部分工作就是把自然语言和ABAP语言做互相转换,这应该也是在ChatGPT ...

  10. 使用Git进行代码版本控制和协作:代码共享、协作和版本管理

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 使用 Git 进行代码版本控制和协作:代码共享.协作和版本管理 Git 是一个开源的分布式版本控制系统,由 Linux 内核开发组创建.G ...