记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理
昨晚我正在床上睡得着着的,突然来了一条短信。

啥,线上MySQL死锁了,我赶紧登录线上系统,查看业务日志。

能清楚看到是这条insert语句发生了死锁。
MySQL如果检测到两个事务发生了死锁,会回滚其中一个事务,让另一个事务执行成功。很明显,我们这条insert语句被回滚了。
insert into user (id, name, age) values (6, '张三', 6);
但是我们怎么排查这个问题呢?
到底跟哪条SQL产生了死锁?
好在MySQL记录了最近一次的死锁日志,可以用命令行工具查看:
show engine innodb status;

在死锁日志中,可以清楚地看到这两条insert语句产生了死锁,最终事务2被会回滚,事务1执行成功。
# 事务1
insert into user (id,name,age) values (5,'张三',5);
# 事务2
insert into user (id,name,age) values (6,'李四',6);
这两条insert语句,怎么看也不像能产生死锁,我们来还原一下事发过程。
先看一下对应的Java代码:
@Override
@Transactional(rollbackFor = Exception.class)
public void insertUser(User user) {
User userResult = userMapper.selectByIdForUpdate(user.getId());
// 如果userId不存在,就插入数据,否则更新
if (userResult == null) {
userMapper.insert(user);
} else {
userMapper.update(user);
}
}
业务逻辑代码很简单,如果userId不存在,就插入数据,否则更新user对象数据。
从死锁日志中,我们看到有两条insert语句,很明显userId=5和userId=6的数据都不存在。
所以对应的SQL执行过程,可能就是这样的:

先用for update加上排他锁,防止其他事务修改当前数据,然后再insert数据,最后发生了死锁,事务2被回滚。
两个事务分别在两个主键ID上面加锁,为什么会产生死锁呢?
如果看过上篇文章,就会明白。
当id=5存在这条数据时,MySQL就会加Record Locks(记录锁),意思就是只在id=5这一条记录上加锁。
当id=5这条记录不存在时,就会锁定一个范围。
假设表中的记录是这样的:
| id | name | age |
|---|---|---|
| 1 | 王二 | 1 |
| 10 | 一灯 | 10 |
select * from user where id=5 for update;
这条select语句锁定范围就是 (1, 10]。
最后两个事务的执行过程就变成了:

通过这个示例看到,两个事务都可以先后锁定 (1, 10]这个范围,说明MySQL默认加的临键锁的范围是可以交叉的。
那怎么解决这个死锁问题呢?
我能想到的解决办法就是,把这两个语句select和insert,合并成一条语句:
insert into user (id,name,age) values (5,'张三',5)
on duplicate key update name='张三',age=5;
大家有什么好办法吗?
这个死锁情况,还是挺常见的,赶紧回去翻一下项目代码有没有这样的问题。
文章持续更新,可以微信搜一搜「 一灯架构 」第一时间阅读更多技术干货。
记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理的更多相关文章
- 一次线上mysql死锁分析
一.现象 发运车次调用发车接口时发生异常,后台抛出数据库死锁日志. 二.原因分析 通过日志可以看出事务T1等待 heap no 8的行锁 (X locks 排他锁) 事务T2持有heap no 8的行 ...
- 记一次linux通过jstack定位CPU使用过高问题或排查线上死锁问题
一.java定位进程 在服务器中终端输入命令:top 可以看到进程ID,为5421的cpu这列100多了. 记下这个数字:5421 二.定位问题进程对应的线程 然后在服务器中终端输入命令:top -H ...
- 原创 记录一次线上Mysql慢查询问题排查过程
背景 前段时间收到运维反馈,线上Mysql数据库凌晨时候出现慢查询的报警,并把原始sql发了过来: --去除了业务含义的sql update test_user set a=1 where id=1; ...
- 线上Mysql数据库崩溃事故的原因和处理
前文提要 承接前文<一次线上Mysql数据库崩溃事故的记录>,在文章中讲到了一次线上数据库崩溃的事件记录,建议两篇文章结合在一起看,不至于摸不着头脑. 由于时间原因,其中只讲了当时的一些经 ...
- Spring+SpringMVC+MyBatis+easyUI整合进阶篇(八)线上Mysql数据库崩溃事故的原因和处理
前文提要 承接前文<一次线上Mysql数据库崩溃事故的记录>,在文章中讲到了一次线上数据库崩溃的事件记录,建议两篇文章结合在一起看,不至于摸不着头脑. 由于时间原因,其中只讲了当时的一些经 ...
- Linux命令排查线上问题常用的几个
排查线上问题常用的几个Linux命令 https://www.cnblogs.com/cjsblog/p/9562380.html top 相当于Windows任务管理器 可以看到,输出结果分两部分, ...
- 记一次线上MySQL数据库死锁问题
最近线上项目报了一个MySQL死锁(DealLock)错误,虽说对业务上是没有什么影响的,由于自己对数据库锁这块了解不是很多,之前也没怎么的在线上碰到过.这次刚好遇到了,便在此记录一下 ...
- 一则线上MySql连接异常的排查过程
Mysql作为一个常用数据库,在互联网系统应用很多.有些故障是其自身的bug,有些则不是,这里以前段时间遇到的问题举例. 问题 当时遇到的症状是这样的,我们的应用在线上测试环境,JMeter测试过程中 ...
- 一次线上Mysql数据库崩溃事故的记录
文章简介 工作这几年,技术栈在不断更新,项目管理心得也增加了不少,写代码的速度也在提升,感觉很欣慰,毕竟是在一直进步,但是过程中也有许许多多的曲折,也踩过了数不尽的坑坑洼洼,从一个连百度都不知道用的萌 ...
随机推荐
- 前端vue之属性指令、style和class、条件渲染、列表渲染、事件处理、数据双向绑定、表单控制、v-model进阶
今日内容概要 属性指令 style和class 条件渲染 列表渲染 事件处理 数据的双向绑定 v-model进阶 购物车案例 内容详细 1.属性指令 <!DOCTYPE html> < ...
- Java语言学习day08--7月7日
###13遍历数组 * A:遍历数组 * 在操作数组时,经常需要依次访问数组中的每个元素,这种操作称作数组的遍历 * B:练习 public class ArrayDemo04 { publ ...
- Envoy熔断限流实践(二)Rainbond基于RLS服务全局限流
Envoy 可以作为 Sevice Mesh 微服务框架中的代理实现方案,Rainbond 内置的微服务框架同样基于 Envoy 实现.本文所描述的全局限速实践也是基于 Envoy 已有的方案所实现. ...
- 抽象类 & 接口
抽象类 模板设计模式 把每个类中类似的job 方法"提炼"出来, 成为一个抽象函数(同时也有一个抽象类) 然后把另外calcuateTime这个函数内容,"提炼" ...
- [AcWing 822] 走方格
点击查看代码 #include<iostream> using namespace std; int n, m, ans = 0; void dfs(int x, int y) { if ...
- 用浏览器快速开启Docker的体验之旅
互联网科技发展创造了很多奇迹,比如我今天要提到的 docker 技术就是其一.我很早就关注它(在2015年写过这方面的博客),那会儿还只是一个开源项目,现在已经是一个行业事实标准了,它推动了云原生的变 ...
- drools的简单入门案例
一.背景 最近在学习规则引擎drools,此处简单记录一下drools的入门案例. 二.为什么要学习drools 假设我们存在如下场景: 在我们到商店购买衣服的时候,经常会发生这样的事情,购买1件不打 ...
- mySql in 语句查询优化
有这么一条sql UPDATE product set BuyerCount =BuyerCount+1 where ProductId in( SELECT ProductId from order ...
- ASP.NET Core + SaasKit + PostgreSQL + Citus 的多租户应用程序架构示例
在 确定分布策略 中, 我们讨论了在多租户用例中使用 Citus 所需的与框架无关的数据库更改. 当前部分研究如何构建与 Citus 存储后端一起使用的多租户 ASP.NET 应用程序. http:/ ...
- mybatis plus 增删改自动填充字段值
说明 本文实现以下需求效果 创建数据时自动填充 createUserId 和 createTime 更新数据时自动填充 updateUserId 和 updateTime(每次修改都自动填充新的 up ...