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

打印 上一主题 下一主题

主题 869|帖子 869|积分 2617

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

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

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

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

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

通过这个示例看到,两个事务都可以先后锁定 (1, 10]这个范围,说明MySQL默认加的临键锁的范围是可以交叉的。
那怎么解决这个死锁问题呢?
我能想到的解决办法就是,把这两个语句select和insert,合并成一条语句:
  1. insert into user (id,name,age) values (5,'张三',5)
  2.         on duplicate key update name='张三',age=5;
复制代码
大家有什么好办法吗?
这个死锁情况,还是挺常见的,赶紧回去翻一下项目代码有没有这样的问题。
文章持续更新,可以微信搜一搜「 一灯架构 」第一时间阅读更多技术干货。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表