首页 > 数据库 >MySQL间隙锁死锁问题

MySQL间隙锁死锁问题

时间:2024-01-26 09:23:06浏览次数:27  
标签:13 间隙 lock 事务 gap MySQL 锁死 id

一、场景还原

当时同事A在线上代码中使用了Mybatis-plus的如下方法
com.baomidou.mybatisplus.extension.service.IService

saveOrUpdate(T, com.baomidou.mybatisplus.core.conditions.Wrapper<T>)
该方法先执行了update操作,如果更新到就不再执行后续操作,如果没有更新到,才进行主键查询,查询到了就修改,未查询到就新增。具体方法如下
/**
     * <p>
     * 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
     * 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作)
     * </p>
     *
     * @param entity 实体对象
     */
    default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
        return update(entity, updateWrapper) || saveOrUpdate(entity);
    }
那么这个方法的做法,为什么会导致间隙锁死锁呢?咱们一起来分析并还原间隙锁死锁的场景。

二、什么是间隙锁

间隙锁是MySQL行锁的一种,与行锁不同的是间隙锁可能锁定的是一行数据,也可能锁住一个间隙。锁定规则如下:
  • 当修改的数据存在时,间隙锁只会锁定当前行。

  • 当修改的数据不存在时,间隙锁会向左找第一个比当前索引值小的值,向右找第一个比当前索引值大 的值(没有则为正无穷),将此区间锁住,从而阻止其他事务在此区间插入数据。

三、间隙锁的作用

与行锁(例如乐观锁高级实现,MVCC)组合成Next-key lock,在可重复读这种隔离级别下一起工作避免幻读。

四、如何关闭间隙锁(强烈不建议关闭)

1、降低隔离级别,例如降为提交读。
2、直接修改my.cnf,将开关,innodb_locks_unsafe_for_binlog改为1,默认为0即开启

五、还原线上间隙锁死锁的场景

5.1 复现间隙锁死锁
5.1.1 我们先准备一个表
mysql> select * from t_gap_lock;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 张一   |   21 |
|  5 | 李五   |   25 |
|  6 | 赵六   |   26 |
|  9 | 王九   |   29 |
| 12 | 十二   |   12 |
+----+--------+------+
表中的id数据咱们准备了三个间隙:
  • 间隙一:1-5

  • 间隙二:6-9

  • 间隙三:12-正无穷

5.1.2 操作
1、此时我们开启事务一,然后执行更新id=3的数据,按照咱们的理论,id=3这个数据不存在,说明它会在1-5之间加间隙锁。
#开启事务一
begin;

#事务一在1-5之间加间隙锁
update t_gap_lock t set t.age = 23 where t.id = 3;
2、然后我们开启事务二,然后执行更新id=7的数据,按照咱们的理论,id=7这个数据不存在,说明它会在6-9之间加间隙锁
#开启事务二
begin;

#事务二在6-9之间加间隙锁
update t_gap_lock t set t.age = 27 where t.id = 7;
3、那么重点来了,此时我们需要做的操作就是让事务一在6-9之间插入数据,会发现此时事务已经被阻塞,无法执行insert,因为事务二已经对该区间加了间隙锁。
#事务一在6-9之间插入数据
insert into t_gap_lock(id, name, age) values(8,'李八',28);
4、在事务一等待锁的同时,咱们让事务二同时在1-5之间插入数据,这个时候会发现,只要事务二一执行插入。MySQL立即报了死锁,我们就会见到如下提示:`[40001][1213] Deadlock found when trying to get lock; try restarting transaction
# 同时事务二在1-5之间插入数据
insert into t_gap_lock(id, name, age) values(3,'李三',23);
5.1.3 整个死锁过程进行原理分析
1、首先事务一开启事务后,更新id=3的数据,此数据不存在,所以事务一会锁住1-5这个间隙,即为1-5这个间隙添加间隙锁,同理,事务二会为6-9这个间隙添加间隙锁;2、然后我们让事务一在6-9这个间隙插入数据,因为事务二已经加了间隙锁,所以事务一需要等待事务二释放间 隙锁才能进行插入操作,此时事务一等待事务二释放间隙锁;3、同理,事务二在1-5间隙插入时需要等待事务一释放间隙锁,两个事务相互等待,死锁产生。那么咱们此时就能大概明白最初那个Mybatis-plus的saveOrUpdate方法为什么会造成间隙锁死锁的问题,也就是线上存在两个并发事务,然后更新的时候都没有更新到,此时都在自己的间隙加了间隙锁,然后再到彼此的区间进行数据插入,此时就会造成两个事务互相等待对方的释放间隙锁,从而导致死锁。也许有同学会想,线上的数据几乎不可能刚好会存在1-5,6-9这种间隙,来给并发事务各自加锁,又刚好到彼此区间插入数据的场景,所以我们就会有接下来验证间隙锁加锁是非互斥的,再一次深度还原间隙锁死锁的场景。
5.2 验证间隙锁加锁非互斥
5.2.1 依然以t_gap_lock为例
mysql> select * from t_gap_lock;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 张一   |   21 |
|  5 | 李五   |   25 |
|  6 | 赵六   |   26 |
|  9 | 王九   |   29 |
| 12 | 十二   |   12 |
+----+--------+------+
5.2.2 操作
1、此时咱们开启事务一,然后执行更新id=13的数据,按照咱们的理论,id=13这个数据不存在,说明它会在13-正无穷(因为当前索引树上没有比13更大的值)之间加间隙锁。
#开启事务一
begin;
#事务一在13-正无穷添加间隙锁
update t_gap_lock t set t.age = 13 where t.id = 13;
2、然后我们开启事务二,然后也执行更新id=13的数据,按照咱们的理论,事务二也会对13-正无穷之间加间隙锁
#开启事务二
begin;
#在13-正无穷添加间隙锁
update t_gap_lock t set t.age = 13 where t.id = 13;
3、那么重点来了,此时我们需要做的操作就是让事务一在13-正无穷之间插入数据,会发现此时事务已经被阻塞,无法执行insert,因为事务二已经对该区间加了间隙锁。
#事务一在13-正无穷中新增数据
insert into t_gap_lock(id, name, age) values (13,'十六',16);
4、在事务一等待锁的同时,咱们让事务二同时在13-正无穷之间插入数据,这个时候会发现,只要事务二一执行插入。MySQL立即报了死锁,我们就会见到如下提示:[40001][1213] Deadlock found when trying to get lock; try restarting transaction
#事务二在13-正无穷中新增数据
insert into t_gap_lock(id, name, age) values (13,'十六',16);
5、因为咱们已经用1-5以及6-9这种明显的间隙还原了间隙锁死锁,所以13-正无穷发生间隙锁死锁的原理与其无异,这里有个非常大的区别就是事务一已经在13-正无穷加了间隙锁,事务二依然可以对此间隙加间隙锁,所以我们用实际证明了间隙锁加锁是非互斥的。此时咱们回忆一下Mybatis-plus的saveOrUpdate方法,发现线上只要出现两个并发事务去修改同一条不存在的数据,就会立马出现间隙锁死锁。
5.3 验证当修改数据存在时,间隙锁只会锁住当前行
还有一个比较重要的点就是,当修改的数据存在时,MySQL只会锁住当前行,咱们一起来分析下整个过程。
5.3.1 依然以t_gap_lock为例
mysql> select * from t_gap_lock;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 张一   |   21 |
|  5 | 李五   |   25 |
|  6 | 赵六   |   26 |
|  9 | 王九   |   29 |
| 12 | 十二   |   12 |
+----+--------+------+
5.3.2 操作
1、此时我们开启事务一,然后执行更新id=12的数据,按照咱们的理论,id=12这个数据存在,说明MySQL只会锁定id=12这一行数据。
#开启事务一
begin;
#事务一只在12上加间隙锁
update t_gap_lock t set t.age = 12 where t.id = 12;
2、然后我们开启事务二,然后执行更新id=13的数据,按照咱们的理论,id=13这个数据不存在,说明它会在13-正无穷(因为当前索引树上没有比13更大的值)之间加间隙锁
#开启事务二
begin;
#事务二在13-正无穷添加间隙锁
update t_gap_lock t set t.age = 13 where t.id = 13;
3、那么重点来了,此时我们需要做的操作就是让事务一在13-正无穷之间插入数据,会发现此时事务已经被阻塞,无法执行insert,因为事务二已经对该区间加了间隙锁。
#事务一在13-正无穷中新增数据
insert into t_gap_lock(id, name, age) values (15,'十五',15);
4、在事务一等待锁的同时,咱们让事务二在12-正无穷之间插入数据,这个时候会发现,事务二能够正常插入,说明事务二没有被间隙锁阻塞,待事务二提交或回滚后,事务一也正常提交。
#事务二在13-正无穷中新增数据
insert into t_gap_lock(id, name, age) values (13,'十六',16);
5、通过以上验证,MySQL在更新id=12,即数据存在时,并没有对12-正无穷添加间隙锁,而是只锁定了id=12这一行数据,从而降低锁的颗粒度以提高性能。

标签:13,间隙,lock,事务,gap,MySQL,锁死,id
From: https://www.cnblogs.com/shujuyr/p/17988598

相关文章

  • MySQL SQL点查,范围查,排序,分组的Explain分析和SQL优化(8.0版本)
    MySQLSQL常用优化主要有where,range,order,groupby,or等查询。下图是优化的原则,后面会有一个例子来看看:比如建立了联合索引(c1,c2,c3),索引长度分别为5,5,4。数据有50条:点查SELECT*FROMtraining.t1wherec3=1andc2=1andc1=1;使用了索引,只要全部包含索引列,那么点查顺序......
  • MySQL学习笔记-d1
    壹·基础篇通用语法及分类DDL:数据定义语言,用来定义数据库对象(数据库、表、字段)DML:数据操作语言,用来对数据库表中的数据进行增删改DQL:数据查询语言,用来查询数据库中表的记录DCL:数据控制语言,用来创建数据库用户、控制数据库的控制权限DDL:1.1数据库CREATEDATABASE......
  • Mysql在Linux运行时新增配置文件提示:World-wrirable config file '/etc/mysql/conf.d/
    场景docker-compose入门以及部署SpringBoot+Vue+Redis+Mysql(前后端分离项目)以若依前后端分离版为例:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/128372122在Centos服务器上使用docker-compose部署mysql后,未配置数据卷映射的my.cnf配置文件。再将服务全部dow......
  • [转帖]一文搞懂各种数据库SQL执行计划:MySQL、Oracle等
    https://zhuanlan.zhihu.com/p/99331255 14人赞同了该文章MySQL执行计划Oracle执行计划SQLServer执行计划PostgreSQL执行计划执行计划(executionplan,也叫查询计划或者解释计划)是数据库执行SQL语句的具体步骤,例如通过索引还是全表扫描访问表中的数据,连......
  • MySQL所有常用语法大全
    MySQL所有常用语法大全一、常用操作数据库的命令1.showdatabases;查看所有的数据库2.createdatabasetest;创建一个叫test的数据库3.dropdatabasetest;删除一个叫test的数据库4.usetest;选中库,在建表之前必须要选择数据库5.showtables;在选中的数据库之中查看所......
  • MySQL100题知识点
    https://testerhome.com/articles/349721、select*查询带来的不确定性2、SELECTemp_name,salary*12+COALESCE(bonus,0)FROMemployee;COALESCE函数用于将空值转换为03、SELECTemp_name,salary*12+COALESCE(bonus,0)AS"全年收入"FROMemployee;AS为......
  • 2024年1月Java项目开发指南4:IDEA里配置MYSQL
    提前声明:文章首发博客园(cnblogs.com/mllt)自动“搬家”(同步)到CSDN,如果博客园中文章发生修改是不会同步过去的,所以建议大家到我的博客园中查看前提条件:1.你已经设计好了数据库,并成功创建了数据库。2.你的springboot项目中已经配置好了MySQL的连接。填写好信息后点测试连......
  • MySQL中,当update修改数据与原数据相同时会再次执行吗?
    一、背景本文主要测试MySQL执行update语句时,针对与原数据(即未修改)相同的update语句会在MySQL内部重新执行吗?二、测试环境MySQL5.7.25Centos7.4三、binlog_format为ROW1、参数 2、测试步骤session1session2session13、总结在binlog_format=row和binlog_row_image......
  • MYSQL数据库同步脚本 --仅供参考
      备份同步数据 #!/bin/bash#定义变量user="root"pass="un1ware"host=""file=$(date+"%Y-%m-%d")#使用日期作为文件夹名称#获取主从状态信息master_status=$(mysql--user="$user"--password="$pass"-h"$host"......
  • MySQL如何快速禁用账户登入 & 如何复制/复用账户密码【转】
    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。作者:叶金荣文章来源:GreatSQL社区原创如何快速临时禁止某账户登入角色ROLES管理需要先激活关于授权的其他几点补充如何复制/复用账户密码1.快......