记一次线上问题 → Deadlock 的分析与优化
开心一刻
今天女朋友很生气
女朋友:我发现你们男的,都挺单纯的
我:这话怎么说
女朋友:脑袋里就只想三件事,搞钱,跟谁喝点,还有这娘们真好看
我:你错了,其实我们男人吧,每天只合计一件事
女朋友:啥事呀?
我:这娘们真好看,得搞钱跟她喝点

问题复现
需求背景
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 的分析与优化的更多相关文章
- 记一次线上bug排查-quartz线程调度相关
记一次线上bug排查,与各位共同探讨. 概述:使用quartz做的定时任务,正式生产环境有个任务延迟了1小时之久才触发.在这一小时里各种排查找不出问题,直到延迟时间结束了,该任务才珊珊触发.原因主要就 ...
- 解Bug之路-记一次线上请求偶尔变慢的排查
解Bug之路-记一次线上请求偶尔变慢的排查 前言 最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章. Bug现场 这是一个偶发的性能问题.在每天几百万比交易请求中,平均 ...
- 记一次线上MySQL数据库死锁问题
最近线上项目报了一个MySQL死锁(DealLock)错误,虽说对业务上是没有什么影响的,由于自己对数据库锁这块了解不是很多,之前也没怎么的在线上碰到过.这次刚好遇到了,便在此记录一下 ...
- 记一次线上Curator使用过程JVM栈溢出解决
为了同学们看起来一目了,特按如下思路进行讲解. 1.出现的场景 2.分析及解决的过程 3.总结 最近公司要使用zookeeper做配置管理(后面简称ZK),然后自己就提前用虚拟机进行 ...
- 记一次线上coredump事故
1.事故背景 上周三凌晨,我负责的某个模块在多台机器上连续发生coredump,幸好发生在业务低峰期,而且该模块提供的功能也不是核心流程功能,所以对线上业务影响比较小.发生coredump后,运维收到 ...
- 记一次线上事故的JVM内存学习
今天线上的hadoop集群崩溃了,现象是namenode一直在GC,长时间无法正常服务.最后运维大神各种倒腾内存,GC稳定后,服务正常.虽说全程在打酱油,但是也跟着学习不少的东西. 第一个问题:为什么 ...
- 记一次线上gc调优的过程
近期公司运营同学经常表示线上我们一个后台管理系统运行特别慢,而且经常出现504超时的情况.对于这种情况我们本能的认为可能是代码有性能问题,可能有死循环或者是数据库调用次数过多导致接口运行 ...
- 记一次线上Kafka消息堆积踩坑总结
2018年05月31日 13:26:59 xiaoguozi0218 阅读数:2018更多 个人分类: 大数据 年后上线的系统,与其他业务系统的通信方式采用了第三代消息系统中间件Kafka.由于是 ...
- 记一次线上由nginx upstream keepalive与http协议"协作"引起的接口报错率飙高事件
年前接到个任务,说要解决线上一些手机客户端接口报错率很高的问题.拿到了监控邮件,粗略一看,各种50%+的错误率,简直触目惊心.这种疑难杂症解决起来还是挺好玩的,于是撸起袖子action. 最终的结果虽 ...
- 记一次线上dubbo服务超时和线程池满问题排查
线上某dubbo服务A调用dubbo服务B的接口X方法,调用端A日志中出现了很多超时的情况,提供端B该接口X超时时间设置为60s: 查看提供端B的日志,报了很多线程池满的异常: Caused by: ...
随机推荐
- Python OOP面向对象编程
OOP 思想: 以模块思想解决工程问题 面向过程 VS 面向对象 由面向过程转向面向对象 例子,我要开一个学校,叫XXX 讲师 学生 班主任 教室 学校 常用名词 OO:面向对象 OOA: 分析 OO ...
- [OpenCV-Python] 13 颜色空间转换
文章目录 OpenCV-Python:IV OpenCV中的图像处理 13 颜色空间转换 13.1 转换颜色空间 13.2 物体跟踪 13.3 怎样找到要跟踪对象的 HSV 值? OpenCV-Pyt ...
- 基于pyinstaller的python打包工具
以下是软件链接:https://mysecreat.lanzoub.com/iZPGf0swgtbc 软件功能:可以对py文件进行打包,功能基于pyinstaller模块,因此需要安装python环境 ...
- 获取scrollTop的方法(兼容所有浏览器)
/** *获取scrollTop的值,兼容所有浏览器 */ function getScrollTop() { var scrollTop = document.documentElement.scr ...
- 2023-02-28:moonfdd/ffmpeg-go是用go语言绑定ffmpeg的库,目前是github上最好用的库。请用go语言将yuv文件编码为h264文件。
2023-02-28:moonfdd/ffmpeg-go是用go语言绑定ffmpeg的库,目前是github上最好用的库.请用go语言将yuv文件编码为h264文件. 答案2023-02-28: 使用 ...
- 2022-12-23:portainer是docker的web可视化工具。如果根据docker部署去写yaml,默认local是k8s,而不是docker,这不符合需求,需要修改yaml。请问部署在
2022-12-23:portainer是docker的web可视化工具.如果根据docker部署去写yaml,默认local是k8s,而不是docker,这不符合需求,需要修改yaml.请问部署在 ...
- 2022-04-29:厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子: 吃掉一个橘子。 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子。 如果剩余橘子数 n 能被
2022-04-29:厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子: 吃掉一个橘子. 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子. 如果剩余橘子数 n 能被 ...
- pandas 数据处理 一些常用操作
读取csv文件,打印列名称: import pandas as pd # data = pd.read_csv("guba_fc_result_20230413.csv") dat ...
- values() 字典形式显示查询结果
values() 字典形式显示查询结果 name,age为数据库的两个列 Student.objects.values('name','age')
- from . import XXX
[Python]from . import XXX 一. 官方文档 sound/ __init__.py formats/ __init__.py wavread.py wavwrite.py ai ...