记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理

记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理

昨晚我正在床上睡得着着的,突然来了一条短信。

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

能清楚看到是这条insert语句发生了死锁。

MySQL如果检测到两个事务发生了死锁,会回滚其中一个事务,让另一个事务执行成功。很明显,我们这条insert语句被回滚了。

insert into user (id, name, age) values (6, ‘张三’, 6);

但是我们怎么排查这个问题呢?

到底跟哪条SQL产生了死锁?

好在MySQL记录了最近一次的死锁日志,可以用命令行工具查看:

show engine innodb status;

在死锁日志中,可以清楚地看到这两条insert语句产生了死锁,最终事务2被会回滚,事务1执行成功。

# 事务1insert into user (id,name,age) values (5,’张三’,5);# 事务2insert 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;

大家有什么好办法吗?

这个死锁情况,还是挺常见的,赶紧回去翻一下项目代码有没有这样的问题。

推荐阅读:《我爱背八股系列》

为什么要用MQ?MQ的作用有哪些? 高并发场景下,如何保证数据的一致性的? 如何进行分库分表?分库分表后有哪些问题以及对应的解决方案。 高并发下怎么生成订单ID?以及每种方案的优缺点。 如何实现分布式锁?使用数据库、分布式数据库、分布式协调服务分别如何实现?

MySQL索引底层数据结构为什么要用B+树?以及红黑树、B树的优缺点。

一篇文章讲清楚MySQL的聚簇/联合/覆盖索引、回表、索引下推

ThreadLocal线上故障复盘,差点丢了工作。详解MySQL事务底层原理一文讲清楚MySQL的所有锁

MySQL update语句加锁过程和原理

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
(0)
用户投稿
上一篇 2022年6月13日
下一篇 2022年6月13日

相关推荐

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息