开心一刻

  今天女朋友很生气

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

  我:这话怎么说

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

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

  女朋友:啥事呀?

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

问题复现

  需求背景

   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. 如何通过C#/VB.NET代码将PowerPoint转换为HTML

    利用PowerPoint可以很方便的呈现多媒体信息,且信息形式多媒体化,表现力强.但难免在某些情况下我们会需要将PowerPoint转换为HTML格式.因为HTML文档能独立于各种操作系统平台(如Un ...

  2. "树形List"与"扁平List"互转(Java实现)

    背景:在平时的开发中,我们时常会遇到下列场景 公司的组织架构的数据存储与展示 文件夹层级的数据存储与展示 评论系统中,父评论与诸多子评论的数据存储与展示 ...... 对于这种有层级的结构化数据,就像 ...

  3. 2021-03-25:如何把长url转换为短url?

    2021-03-25:如何把长url转换为短url? 福大大 答案2021-03-25: 1.长url和短url一一映射.想采用某种算法,把长url和短url做一一映射.后来发现,这种方法是行不通的. ...

  4. 2021-12-02:给定一个字符串str,和一个正数k。 返回长度为k的所有子序列中,字典序最大的子序列。 来自腾讯。

    2021-12-02:给定一个字符串str,和一个正数k. 返回长度为k的所有子序列中,字典序最大的子序列. 来自腾讯. 答案2021-12-02: 单调栈.先进来的元素大,后进来的元素小. 时间复杂 ...

  5. 百度飞桨(PaddlePaddle) - PaddleOCR 文字识别简单使用

    百度飞桨(PaddlePaddle)安装 OCR 文字检测(Differentiable Binarization --- DB) OCR的技术路线 PaddleHub 预训练模型的网络结构是 DB ...

  6. reverse逆转,即反向排序

    reverse逆转,即反向排序 print(Student.objects.all().exclude(nickname='A').reverse()

  7. 安装vue cli3以及配置环境 镜像下载

    安装vue cli3以及配置环境 镜像下载 1.下载安装Node.js 安装vue cli3之前需要先安装Node.js,方便对vue进行下载 node.js的下载与安装方法在隔壁哟 地址:https ...

  8. 在DevExpress中使用BandedGridView表格实现多行表头的处理

    在之前较早随笔中介绍过实现多行表头的处理,通过手工创建字段以及映射数据源字段属性的方式实现,有些客户反映是否可以通过代码方式更方便的创建对应的处理操作,因此本篇随笔继续探讨这个多行表头的处理的操作,使 ...

  9. K8s Pod状态与容器探针

    1.pod的调度流程及常见状态 1.1.pod的调度流程 Pod创建过程如上图所示,首先用户向apiserver发送创建pod的请求,apiserver收到用于创建pod请求后,对应会对该用户身份信息 ...

  10. 【网络知识】虚拟机的桥接、NAT、仅主机模式分别是什么?

    在我们安装 VMware 时,VMware 会自动三种 3 种网络连接模式,分别为VMnet0 (桥接模式).VMnet8 (NAT模式).VMnet1 (仅主机模式),当然我们也可以根据需要自行创建 ...