- 浏览: 1146345 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (138)
- JAVA基础 (22)
- Spring (6)
- 设计模式 (2)
- JDK源码 (3)
- java-功能组件 (4)
- 游戏项目 (2)
- linux (13)
- Oracle (2)
- struts (1)
- 字符集 (8)
- HTTP协议 (2)
- java-网络通信 (1)
- 工具软件推荐 (2)
- tomcat (1)
- java-容器框架 (2)
- java-IO框架 (2)
- java-多线程框架 (4)
- java-NIO框架 (0)
- jquery (2)
- 工具使用 (12)
- 加密解密 (1)
- redis (2)
- maven (2)
- svn (1)
- eclipse (1)
- mysql (11)
- 我的收藏 (1)
- JAVA进阶 (26)
- 运维 (3)
- protocol buffer (1)
- 优秀博主 (1)
- nginx (1)
- 算法 (2)
- 故障排查 (4)
- 粤语歌曲 (6)
- 生活总结 (6)
- 高并发 (4)
- 语言训练 (1)
- 读书笔记 (5)
- 诗歌 (1)
- tomcat源码学习 (1)
- 软件词汇 (1)
- git (1)
最新评论
-
ryuhi:
一个是来源source,一个是来源方序列号seq这两个数据要怎 ...
高并发的核心技术-幂等的实现方案 -
xuezhongyu01:
无量 写道Master-Gao 写道理论感觉还行,可以代码我还 ...
高并发的核心技术-幂等的实现方案 -
无量:
Master-Gao 写道理论感觉还行,可以代码我还是不会写。 ...
高并发的核心技术-幂等的实现方案 -
phil_jing:
@RequestParam 默认 true
SpringMVC注解@RequestParam全面解析 -
aguai0:
aguai0 写道第五条里的如果要获取任务执行结果,用Comp ...
JAVA进阶----ThreadPoolExecutor机制
一次mysql死锁的排查过程
一次mysql死锁的排查过程
一、背景
17号晚上要吃饭了,看旁边的妹子和佐哥还在调代码,就问了下什么问题啊,还在弄,妹子说,在测试环境测试给用户并发发送卡券时,出现了死锁,但看代码没有死锁,问题如下图
看日志确实发生了死锁,按照死锁产生的原因:一般死锁是两把锁两个人争抢,每个人都获得其中一把,谁都不让谁,等待对方释放锁,死循环导致的,图示如下
不过这次说看代码没有问题,感觉这个问题比较诡异,跟他们说先吃饭,吃完,一起群力群策研究研究这个。
二、问题点
1. ### SQL: select * from score_user where user_id = ? for update,这个sql查询是发送了死锁
三、排查过程
1. 根据经验和死锁产生的条件,猜测代码并发执行,一个线程先锁住了表A的记录,另外一个线程由于原因没有线索表A记录,而锁住了表B的记录,接下来,锁住A记录的线程等待B的锁是否,锁住B的线程等待A的锁释放,所以产生了原因,所以先看代码
2. 代码如下面所示,可以看到,基本逻辑,都是先插入score_gain_stream
3. 看代码,不会发生死锁的,多个线程同时在执行,每个线程都开启事务,每个线程都加锁查询score_user,发现都没有查询到,那么每个线程都执行插入score_gain_stream操作,都成功,接下来,进行插入score_user,这里面只有一个线程可以成功,有唯一主键,其他线程这里会报错,接下来代码抓取异常,进行加锁查询,此时报错,死锁了
4. 理论上,报错,这里没有涉及争抢资源的情况,大家都在等待score_user释放,就一个锁,怎么会死锁呢,看来代码解决不了问题了
5. 再去查下mysql的死锁日志,看看死锁具体怎么产生的,如下图链接如何查询死锁日志
http://825635381.iteye.com/blog/2339503
看紫色中的三部分,
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]
原理分析:这就找到上面问题为什么加上S锁的问题,当并发插入时,出现duplicate异常时,mysql会默认加上S锁,这就是为什么会出现死锁日志里面有个事务加上S锁了,也就同时解释了第二个问题,为什么事务没能提交,因为第一个事务也发生了duplicate异常,同时也对同一个位置加上了S锁,这样就出现了一种情况,多个线程对同一个位置持有S锁,每个线程都去这个位置争抢X锁,S和X锁两者是互斥关系,所以出现循环等待,死锁就此产生
关于mysql锁的机制,单独写个博客来介绍
四、解决办法
1. 并发插入时,不在一个事务内进行再次事务提交
2. 通过其他手段,如预创建账户,解决这个要并发插入的问题
3. 改并发为串行执行
五、解决过程
六、问题总结
1. mysql并发插入,出现duplicate时,会默认加S锁,这个坑啊,坑啊,要研究下为什么这么加
七、为什么会发生?
1. 知识体系,需要再次完善,技术无止境
欢迎大家关注我的公众号:
一、背景
17号晚上要吃饭了,看旁边的妹子和佐哥还在调代码,就问了下什么问题啊,还在弄,妹子说,在测试环境测试给用户并发发送卡券时,出现了死锁,但看代码没有死锁,问题如下图
看日志确实发生了死锁,按照死锁产生的原因:一般死锁是两把锁两个人争抢,每个人都获得其中一把,谁都不让谁,等待对方释放锁,死循环导致的,图示如下
不过这次说看代码没有问题,感觉这个问题比较诡异,跟他们说先吃饭,吃完,一起群力群策研究研究这个。
二、问题点
1. ### SQL: select * from score_user where user_id = ? for update,这个sql查询是发送了死锁
三、排查过程
1. 根据经验和死锁产生的条件,猜测代码并发执行,一个线程先锁住了表A的记录,另外一个线程由于原因没有线索表A记录,而锁住了表B的记录,接下来,锁住A记录的线程等待B的锁是否,锁住B的线程等待A的锁释放,所以产生了原因,所以先看代码
2. 代码如下面所示,可以看到,基本逻辑,都是先插入score_gain_stream
@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 < 0, userId:" + userId + "scoreRuleId:" + scoreRuleId + ",scoreWay:" + scoreWay); 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
看紫色中的三部分,
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]
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. 知识体系,需要再次完善,技术无止境
欢迎大家关注我的公众号:
发表评论
-
区块链!每个人都要了解下--十分钟洞见区块链的前世今生
2018-04-27 12:32 797区块链!每个人都要了解下--十分钟洞见区块链的前世今生 ... -
区块链!每个人都要了解下--十分钟洞见区块链的前世今生
2018-04-27 12:09 0为啥要讲区块链呢,因为它太火了,火到什么程度呢 ... -
项目打包,报软件包、类不存在问题排查过程
2017-05-16 17:13 5978项目打包报,软件包、类不存在问题排查过程 一、背景 ... -
海量数据存储--分库分表策略详解
2017-04-12 19:59 7331海量数据存储--分库分表策略详解 一、背景: 系统刚 ... -
jstack详解
2017-02-17 11:15 1914jstack http://www.open-open.co ... -
jdk-源码中的一些坑
2017-02-13 15:17 1231jdk-源码中的一些坑 1. Runnable接口的命名简直 ... -
mysql常用命令
2016-11-21 17:42 8411. 登录 mysql -h xxxx.xxx.xxx -P ... -
如何查看MySQL数据库的死锁日志
2016-11-21 15:10 41264如何查看MySQL数据库的 ... -
JVM调优:选择合适的GC collector (三)
2016-11-15 20:51 1270CMS Collector 在很多地方,CMS Collec ... -
JVM调优:选择合适的GC collector (二)
2016-11-15 20:47 995http://blog.csdn.net/historya ... -
JVM调优:选择合适的GC collector (一)
2016-11-15 20:45 1271http://blog.csdn.net/historyas ... -
jstat查看gc情况
2016-11-10 10:11 2493jps(Java Virtual Machine Proces ... -
tomcat源码学习(一) eclipse导入tomcat源码
2016-10-31 20:05 14031. 到官网下载Tomcat源代码,这里用到的是apache- ... -
深入分析ClassLoader
2016-10-27 23:27 781转(原文http://blog.csdn.net/xya ... -
业务架构模板
2016-10-20 19:56 1884业务架构模板 默认一个高大上的业务系统需要具备的技术点和对应 ... -
如何写一个强壮的JOB任务
2016-10-18 15:00 3013如何写一个强壮的JOB任务 1. JOB跑一半断电了,不能产 ... -
mybatis.xml中sql编写规范
2016-10-18 14:54 6258一、越少的代码,越强悍的功能,xml里面应该6个sql语句就够 ... -
数据库设计规范
2016-10-17 23:29 65791. 数据库设计基本规范 领域驱动表内容 ... -
全局主键生成器-支持单JVM1秒近1000万订单生成
2016-05-03 20:46 5673全局主键生成器 介绍: 相对于DB自增序列的全局主键生成器, ... -
解决并发下累计的问题
2016-04-25 11:58 2125package com.tongbanjie.trade.te ...
相关推荐
本文将详细记录一次特定的MySQL死锁排查过程,以便于理解和处理类似的问题。 首先,死锁的基本概念是:当两个事务A和B相互持有对方需要的锁时,就会发生死锁。在事务A等待事务B释放锁的同时,事务B也在等待事务A...
本篇文章将深入探讨一次神奇的MySQL死锁排查记录,通过具体的案例帮助读者理解和解决这类问题。 在描述中提到的背景,我们了解到作者之前对MySQL锁机制有一定的了解,但在处理一个特定的死锁问题时遇到了挑战。问题...
MySQL中的死锁问题是一个复杂而微妙的议题,尤其是在数据库优化和并发控制中。本文将深入探讨一个看似不可能发生的死锁案例,并分析其背后的原理。死锁通常发生在多个事务相互等待对方释放资源的情况下,但在这个...
标题和描述中提到的事件是一次在线MySQL数据库发生的死锁问题,这突显了了解数据库加锁原理的重要性,而不仅仅是基础的CRUD操作。死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种相互等待的现象,若无...
本文将探讨MySQL死锁的产生原因以及解决方案。 **死锁的产生原因** 1. **资源竞争与顺序依赖**:当两个事务A和B分别持有对方需要的资源,A等待B释放资源,B也在等待A释放资源,就会形成死锁。例如,事务A锁定表A后...
避免这种死锁的一种方法是在设计事务时遵循一定的顺序规则,例如,确保所有事务按照相同的顺序访问和修改数据,或者使用`FOR UPDATE SKIP LOCKED`(Oracle支持,但MySQL不直接支持)来跳过已锁定的行。另外,可以...
MySQL的InnoDB存储引擎在处理并发事务时可能会遇到死锁问题,这主要发生在多个事务互相等待对方释放资源的情况下。死锁通常由四个必要条件引发: 1. 互斥条件:资源只能被一个事务使用。 2. 请求和保持条件:一个...
MySQL数据库在运行过程中可能会遇到各种问题,其中死锁和日志分析是排查和解决数据库异常的关键环节。在本文中,我们将深入探讨MySQL的死锁现象以及如何通过日志来定位和解决问题。 首先,死锁是数据库操作中的一种...
因此,这个工具可能是为这些数据库系统设计的,用于检测和解除SQL查询过程中出现的死锁。 在压缩包子文件的文件名称列表中,我们看到的是一些动态链接库(DLL)文件和一个可执行文件(find_deadlock.exe)。这些DLL...
3. **锁机制问题**:死锁是导致CPU飙升的一个重要原因。MySQL的InnoDB引擎支持事务和行级锁,不正确的锁策略可能导致死锁。通过`jstack`分析,若发现"deadlock"关键字,说明存在死锁,需要检查事务的执行顺序和锁的...
这也是非常不好的,因为每次建立一个数据库连接都很耗时,好不容易建立好了连接,执行完了SQL语句,你还把数据库连接给销毁了,下一次再重新建立数据库连接,那肯定是效率很低下的! 因此,一般我们必须要使用一个...
MySQL数据库虽然没有内置的死锁日志,但在InnoDB存储引擎中,当发生死锁时,会自动检测并回滚其中一个事务,同时记录在"SHOW ENGINE INNODB STATUS"的输出中。通过对这些信息的分析,可以了解死锁的详细过程。 除了...
《MySQL排错指南》是一本专门针对MySQL数据库系统在运行过程中遇到问题的解决方法和技巧的书籍。PDF格式使得读者可以方便地在电子设备上阅读和检索内容。MySQL作为世界上最流行的开源关系型数据库管理系统,其广泛...
MySQL在检测到死锁时会回滚其中一个事务以解决死锁。通过`SHOW ENGINE INNODB STATUS`可以查看死锁详情。 解决这种问题的一种方法是调整事务的隔离级别。MySQL默认的隔离级别是可重复读(REPEATABLE-READ),而...