go语言 mysql卡死_一次mysql死锁的排查过程-Go语言中文社区

一次mysql死锁的排查过程一、背景17号晚上要吃饭了,看旁边的妹子和佐哥还在调代码,就问了下什么问题啊,还在弄,妹子说,在测试环境测试给用户并发发送卡券时,出现了死锁,但看代码没有死锁,问题如下图

3882eaf941155b4800cd9e99804d5146.png看日志确实发生了死锁,按照死锁产生的原因:一般死锁是两把锁两个人争抢,每个人都获得其中一把,谁都不让谁,等待对方释放锁,死循环导致的,图示如下

5a18ff0a77538bce74e7a5103588e0b1.png不过这次说看代码没有问题,感觉这个问题比较诡异,跟他们说先吃饭,吃完,一起群力群策研究研究这个。二、问题点1. ### SQL: select * from score_user where user_id = ? for update,这个sql查询是发送了死锁三、排查过程1. 根据经验和死锁产生的条件,猜测代码并发执行,一个线程先锁住了表A的记录,另外一个线程由于原因没有线索表A记录,而锁住了表B的记录,接下来,锁住A记录的线程等待B的锁是否,锁住B的线程等待A的锁释放,所以产生了原因,所以先看代码2. 代码如下面所示,可以看到,基本逻辑,都是先插入score_gain_stream

Java代码  62d2a4b1c06971600377178cae2023da.png

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,rollbackFor = Exception.class)

public boolean generateScoreInfo(String userId, Integer score,

Long scoreRuleId, int scoreType, int scoreStatus, String scoreWay,

String orderId, String inviteeId, String reqId, Integer eventVersion) {

//0:参数判断

if(null == score || score <= 0) {

log.warn("score null or 

return true;

}

//1:获取用户等级

int memberLevel = MemberLevel.GENERAL_MEMBER;

ScoreUser dbScoreUser = scoreUserManager.getScoreUserByUserIdForUpdate(userId);

boolean isCreate = null == dbScoreUser ? true : false;

if (!isCreate) {

memberLevel = dbScoreUser.getMemberLevel();

}

// 2:构造/生成积分流水

ScoreGainStream scoreGainStream = contructSocreGainStream(userId, score, scoreRuleId, scoreType, scoreStatus, scoreWay,

orderId, inviteeId, reqId, eventVersion,memberLevel);

boolean streamFlag = addScoreGainStream(scoreGainStream);

if(!streamFlag){

log.error("addScoreGainStream error,data:" + scoreGainStream.toString());

return false;

}

// 3:判断用户类型

if(isCreate){//新增积分用户信息

try {

boolean addFlag = addScoreUser(userId, memberLevel, scoreType, score);

if(!addFlag){

log.error("generateScoreInfo addScoreUser error, userId:" + userId + "|" + "score:" + score );

throw new RuntimeException("generateScoreInfo addScoreUser error");

}

} catch (Exception e) {

if(e instanceof DuplicateKeyException){

log.warn("addScoreUser DuplicateKeyException,userId:" + userId + "|" + "score:" + score);

//查询用户信息

ScoreUser updateUser = contructUpdateScoreUser(scoreUserManager.getScoreUserByUserIdForUpdate(userId), score, scoreStatus);

boolean flag = scoreUserManager.updateUserScoreInfoById(updateUser) > 0 ? true : false;

if(!flag){

log.error("generateScoreInfo updateUserScoreInfoById error, data:" + updateUser.toString());

throw new RuntimeException("generateScoreInfo updateUserScoreInfoById error");

}

return true;

}else{

log.error("addScoreUser error,userId:" + userId + "|" + "score:" + score, e);

return false;

}

}

return true;

}else{//更新积分用户信息

ScoreUser updateScoreUser = contructUpdateScoreUser(dbScoreUser, score, scoreStatus);

boolean flag = scoreUserManager.updateUserScoreInfoById(updateScoreUser) > 0 ? true : false;

if(!flag){

log.error("generateScoreInfo updateUserScoreInfoById error, data:" + updateScoreUser.toString());

throw new RuntimeException("generateScoreInfo updateUserScoreInfoById error");

}

return true;

}

}

3. 看代码,不会发生死锁的,多个线程同时在执行,每个线程都开启事务,每个线程都加锁查询score_user,发现都没有查询到,那么每个线程都执行插入score_gain_stream操作,都成功,接下来,进行插入score_user,这里面只有一个线程可以成功,有唯一主键,其他线程这里会报错,接下来代码抓取异常,进行加锁查询,此时报错,死锁了4. 理论上,报错,这里没有涉及争抢资源的情况,大家都在等待score_user释放,就一个锁,怎么会死锁呢,看来代码解决不了问题了5. 再去查下mysql的死锁日志,看看死锁具体怎么产生的,如下图链接如何查询死锁日志http://825635381.iteye.com/blog/2339503

00c80653a8b6cb24a1566336851c12eb.png看紫色中的三部分,TRANSACTION 1292943095需要RECORD LOCKS space id 553 page no 376 n bits 368 index `index_user_id` of table `tbj`.`score_user这个位置的X锁,一直等待这个X锁TRANSACTION 1292943097这个已经持有RECORD LOCKS space id 553 page no 376 n bits 368 index `index_user_id` of table `tbj`.`score_user这个位置的S锁,这样导致TRANSACTION 1292943095无法在这个位置获得X锁TRANSACTION 1292943097这个事务接下来也在RECORD LOCKS space id 553 page no 376 n bits 368 index `index_user_id` of table `tbj`.`score_user这个位置的等待X锁所以问题点有了: 

1. 为什么有一个线程会持有S锁,看前面的代码结构没有加过S锁?

2. 还有为什么TRANSACTION 1292943097这个事务不能继续加X锁提交?6.这边开始排查为什么会有S锁,查了很多资料,终于,在官网文档查询到了,如下[b]

Java代码  62d2a4b1c06971600377178cae2023da.png

INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.Prior to inserting the row, a type of gap lock called an insertion intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.

大体的意思是:insert会对插入成功的行加上排它锁,这个排它锁是个记录锁,而非next-key锁(当然更不是gap锁了),不会阻止其他并发的事务往这条记录之前插入记录。在插入之前,会先在插入记录所在的间隙加上一个插入意向gap锁(简称I锁吧),并发的事务可以对同一个gap加I锁。如果insert 的事务出现了duplicate-key error ,事务会对duplicate index record加共享锁。这个共享锁在并发的情况下是会产生死锁的,比如有两个并发的insert都对要对同一条记录加共享锁,而此时这条记录又被其他事务加上了排它锁,排它锁的事务提交或者回滚后,两个并发的insert操作是会发生死锁的。

[/b]原理分析:这就找到上面问题为什么加上S锁的问题,当并发插入时,出现duplicate异常时,mysql会默认加上S锁,这就是为什么会出现死锁日志里面有个事务加上S锁了,也就同时解释了第二个问题,为什么事务没能提交,因为第一个事务也发生了duplicate异常,同时也对同一个位置加上了S锁,这样就出现了一种情况,多个线程对同一个位置持有S锁,每个线程都去这个位置争抢X锁,S和X锁两者是互斥关系,所以出现循环等待,死锁就此产生关于mysql锁的机制,单独写个博客来介绍四、解决办法1. 并发插入时,不在一个事务内进行再次事务提交2. 通过其他手段,如预创建账户,解决这个要并发插入的问题3. 改并发为串行执行五、解决过程六、问题总结1. mysql并发插入,出现duplicate时,会默认加S锁,这个坑啊,坑啊,要研究下为什么这么加七、为什么会发生?1. 知识体系,需要再次完善,技术无止境

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/551755.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python制作收费标准_在Python中做一次简单定制1

一元操作符pyhon 支持的一元操作符&#xff1a;__neg__()表示正号行为&#xff1a;__pos__()表示定义负号行为;而__abs__()表示定义abs()函数(取绝对值)被调用时的行为&#xff1a;__inver__()表示定义按位取反的行为。简单定制下面一起来做一个案例。基本要求&#xff1a;#定制…

怎么格式化电脑_U盘格式化后数据能恢复吗?人人都能学会的恢复方法!

获取专业数据恢复软件&#xff1a;专注硬盘U盘误删文件数据恢复软件免费下载​dl-next.aunbox.cn数据恢复官网&#xff1a;嗨格式数据恢复大师官网 - 专业U盘/电脑/硬盘数据恢复软件_免费下载​huifu.hgs.cnU盘格式化后数据能恢复吗&#xff1f;U盘在我们生活中算是比较常用的数…

java 运算符_详解Java表达式与运算符

课程导言【变量的赋值与计算都离不开表达式&#xff0c;表达式的运算依赖于变量、常量和运算符。本节课讨论Java的表达式的构成、常量的定义、运算符的分类及应用。通过本课的学习你将掌握运用表达式和运算符完成变量赋值、条件判断、数学运算、逻辑运算等功能操作】在讲述课程…

将你一张表的值覆盖_精准度可达亚米级,山东“北斗一张网”向社会免费开放...

齐鲁晚报齐鲁壹点记者张阿凤通讯员苏彬8月21日&#xff0c;山东省北斗卫星导航定位基准站网(以下简称“北斗一张网”)推广应用座谈会在济南举行。“北斗一张网”自2019年8月建成后&#xff0c;现已免费向社会提供实时、动态、高精度的卫星导航定位基础服务&#xff0c;在自然资…

okhttp 工具类_日语学习工具推荐,小白必备!

有很多同学问我&#xff0c;有没有好用的日语学习工具&#xff1f;当然有啦~这些学习工具大概分为辞典类、翻译类、日语知识类等等。今天呢&#xff0c;在自身使用的工具里挑选了下面8个学习工具网站&#xff0c;适合各个学习阶段&#xff0c;从日语初级到精通&#xff0c;它们…

java 且_JAVA中逻辑运算符“|”和“”与“||”和“”的用法

1.使用规则&#xff1a;(1)& 可以用作逻辑与的运算符&#xff0c;表示逻辑与(and)a、当运算符两边的表达式的结果都为true时&#xff0c;整个运算结果才为true&#xff0c;否则&#xff0c;只要有一方为false&#xff0c;则结果为falseb、无论第一个判断条件是否成立&#…

des和aes相比较有哪些特点_栓流气力输送相比较传统的高速气力输送方式而言,有哪些优势?...

南京翔瑞粉体&#xff1a;“气力输送”是指运用物料载体压缩空气&#xff08;或气体&#xff09;&#xff0c;将散装物料从一处输送到另一处的输送系统&#xff0c;与传统的输送机械相比&#xff0c;气力输送系统在输送过程中不易出现沾染粉尘、受潮、污损等现象&#xff0c;并…

32位java虚拟机_微软Java虚拟机-Microsoft VM32位下载V5.0.3805.0安装版-MicrosoftJavaVirtualMachine西西软件下载...

Microsoft VM32位是一款微软Java虚拟机(Microsoft Java Virtual Machine)&#xff0c;为IE浏览器提供Java支持。Microsoft VM for Java 是在由Microsoft开发类别 Servers Shareware 软件。安装在WinNT上之前必须打有 NT4 Service Pack 3 以上。安装检测检查是否已安装 Microsof…

iframe src 传参数_剧本杀测评|本友投稿——蜀山传(非剧透)

点击蓝字关注“我爱剧本杀”以下内容不会剧透请放心阅读本友投稿——《蜀山传》前言&#xff1a;这一次的“本友测评”真的是早早早早早鸟测评了&#xff0c;更加难得的是&#xff0c;这一次是曾经制作发行过《夜来香》的发行团队小本买卖和作者紧张齐聚南宁并亲自带本&#xf…

amr转换成mp3 java_java将amr文件转换为MP3格式(windowslinux均可使用,亲测)

使用场景&#xff1a;业务中需要使用微信语音接口&#xff0c;由于微信上传语音只保存3天&#xff0c;所以需要将语音文件下载到服务器。但是amr格式文件&#xff0c;前端无法识别&#xff0c;需要将其转换为mp3格式。装换方法如下&#xff1a;1、需安装ffmpeg软件(安装方法自行…

gnss单频软件接收机应用与编程_GNSS/GPS RTK定位 (手机,无人车定位,无人驾驶,因子图优化)...

Global navigation satellite system (GNSS)是手机或者无人车定位中的关键一个部分。GNSS是当前主要的可以提供绝对定位信息的一种信息来源。无人车的基于地图匹配定位的这一个部分中&#xff0c;GNSS经常用来提供初始化。就目前来看&#xff0c;GNSS的定位方式主要包括单点定位…

windows系统改装为linux系统_Linux怎么克隆系统?备份系统跟Windows系统有区别吗?...

请关注本头条号&#xff0c;每天坚持更新原创干货技术文章。如需学习视频&#xff0c;请在微信搜索公众号“智传网优”直接开始自助视频学习1. 前言本文主要讲解在Linux系统中怎么克隆系统镜像、克隆分区、整盘克隆。它跟Windows系统有何区别&#xff1f;您可能想要克隆Linux分…

java 计算随机数_JAVA获取随机数

在Java中我们能够使用java.util.Random类来产生一个随机数发生器。它有两种形式的构造函数&#xff0c;各自是Random()和Random(long seed)。Random()使用当前时间即System.currentTimeMillis()作为发生器的种子&#xff0c;Random(long seed)使用指定的seed作为发生器的种子。…

前端处理带t的时间_适合家用的跑步机?阿迪达斯轻爵T-19i跑步机测评|adidas|商用机...

(原标题&#xff1a;适合家用的跑步机&#xff1f;阿迪达斯轻爵T-19i跑步机测评) 现在的跑步机市场上&#xff0c;商用高端跑步机跑带更长、电机更强。这是为了满足健身房开业时间与不同人群跑步需求&#xff0c;但机器体积都太大腾挪太方便&#xff0c;且起码要万元以上。而家…

c++ hough变换代码_hough变换原理以及实现(转载)

原理链接如下&#xff1a;陌归&#xff1a;霍夫&#xff08;Hough&#xff09;变换之直线检测代码链接&#xff1a;Ganso&#xff1a;Fundamentals——从车道线检测谈到霍夫变换同样是一篇讲解原理的番外&#xff0c;这一篇主要讲解CV中常用的霍夫变换的数学原理。霍夫变换的由…

java filter url匹配规则_java过滤器filter使用

一&#xff1a;filter:过滤器&#xff0c;拦截servlet的请求和响应。1、1 package jd.com.filter;23 import javax.servlet.*;4 import java.io.IOException;56 public classMyFilter implements Filter {7 Override8 public voiddestroy() {910 }1112 Override13 public voidi…

怎么对document.write写出来的内容调整对齐方式_干不过写PPT的?麦肯锡老阿姨教你4招...

PPT是我真正花过10,000小时以上的技能&#xff0c;毕竟在麦肯锡呆了7年。麦肯锡PPT重视内容与逻辑&#xff0c;我改行做营销后又开始注意视觉效果。怎么干过我们这些画PPT的&#xff1f;&#xff1a;)从内容到形式&#xff0c;我来讲讲4步流程&#xff0c;有助于提高效率。/ 01…

iphone查看删除的短信_苹果删除的短信

苹果删除的短信怎么恢复?大家在使用手机的时候&#xff0c;有些没用的短信就习惯清除了&#xff0c;但是有时候比较重要的短信很可能也会误删&#xff0c;后期想要找确又找不到&#xff0c;那么苹果删除的短信能恢复吗?怎么恢复呢?下面就来详细介绍一下。苹果删除的短信怎么…

pythonrequests证书_python requests证书问题解决

用requests包请求https的网站时&#xff0c;我们偶尔会遇到证书问题。也就是常见的SSLerror&#xff0c;遇到这种问题莫慌莫慌。这里没有找到合适的网站去报SSL证书的错误&#xff0c;所以就假装请求了一个https的网站&#xff0c;然后给报了SSLerror了&#xff0c;然后下面是解…

vscode代码运行时间工具_代码编辑器横评:为什么 VS Code 能拔得头筹

2015 年 4 月 29 日的 Build 大会上&#xff0c;微软发布了 Visual Studio Code 第一个预览版本。短短四年时间里&#xff0c;VS Code 高速成长。根据 2019 年 2 月的 PYPL Top IDE index 的排名&#xff0c;VS Code 的涨势迅猛&#xff0c;在所有编辑器与 IDE 中排名第六&…