Mysql 中当引擎是InnoDB时,锁的使用总结

今天做项目时,碰到一个业务并行处理的问题,因为当前使用的数据库是mySql,且引擎采用的是InnoDB,所以只对InnoDB下锁的使用进行了测试和总结:

查询所用的锁:

1)普通的查询没有涉及到任何锁的使用,查询的结果根据数据隔离级别相关(mySql默认使用的隔离级别是可重复读——
Repeatable read,而且由于排它锁的存在,幻读的情况基本不会存在——对于InnoDB,批量更新一定采用的是表排它锁,
所以另一个事物中的插入操作只能在当前事物提交后才能执行,所以不会在一个事务中查询到未更新的数据)(文档参考:https://www.cnblogs.com/fjdingsd/p/5273008.html);

2)查询加共享锁——"SELECT ... LOCK IN SHARE MODE"
  对于加了共享锁的数据行,其他事务可以加共享锁或不加锁,但无法加排它锁.
  (update,delete,insert都会自动给涉及到的数据加上排他锁)

3)查询加排它锁 -"SELECT  …  FOR UPDATE"
   对于加排它锁的数据行,其他事务可以不加锁,但无法加排它锁和共享锁.
  (update,delete,insert都会自动给涉及到的数据加上排他锁)

注:InnoDB的行锁实现方式(共享锁和排它锁都适用此规则)
InnoDB行锁是通过给索引上的索引项加锁来实现的, InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

以上是参考别人的文章和自己的测试进行的总结;

下面来描述一个奇怪的问题:
这里写图片描述
这里写图片描述
可以看出,表中共10条数据;

1、测试1
开启两个事物:
这里写图片描述

事物A先执行插入操作,事物B进行加入共享锁的查询操作:
这里写图片描述

可以看出事物B在等待;
提交事物A,则B可查询出结果;
这里写图片描述
事物B查询出的结果为11条,比开启事物前多了一条,也就是说事物B读取了A中提交的数据

2、测试2
而如果事物B采用无锁查询时:
插入前(两边同时开启事物,B事物先查询操作)
这里写图片描述
由图可知,表中目前有11条数据,然后A事物进行插入操作,B事物进行无锁查询;
这里写图片描述
可以看出A事物执行插入后未提交事物,事物B查出的数据没有改变;

然后A事物提交,B事物再查询;
这里写图片描述
事物B查询的数据仍然没有改变

当B事物提交后,再次查询,数据就变为12条了;
这里写图片描述
这里写图片描述

由上述描述可知,事物B采用无锁查询时,符合mySql的事物隔离级别(可重复读),如果采用共享锁的方式就与mySql设置的隔离级别不一致(不可重复读),至于为什么,我也没找到相关的文档,待解,大家以后在使用时,可以留意下,别掉进坑里。

上述描述有误:之前对事物的隔离理解有误,现纠正:mysql默认采用的隔离级别是可重复读,而上文用例1的成功,并不能证明是可重复读,因为普通的查询是采用快照读,所以第二次读的是缓存中的数据,而采取加锁读时,读的是最新版本,因此当插入操作的事物提交后,读到了最新插入的数据;可重复读主要针对修改操作。如果对于普通的查询操作,在默认的隔离级别下,幻读也不会发生,因为读的数据始终是开启事物时的数据库快照内容。在隔离级别是可重复性读(RR)下,并且innodb_locks_unsafe_for_binlog的值为OFF时,InnoDB默认使用的就是使用临键锁(next-key lock),所以也不会出现幻读。

参考:解析InnoDB锁类型及使用方式 https://www.jianshu.com/p/abc2e43f6f9e

补充:
1、测试1如果B开启事物后先加锁查询,那么A就无法更新或删除B中所查询的数据,那么就不会出现不可重复读的问题;

2、对于测试1所出现的不可重复读问题,是由于在MVCC(技术叫做数据多版本并发控制 MultiVersion Concurrency Control)并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
所以普通的查询操作属于快照读,读取的是事物开始状态下的数据记录,可能是历史版本;而加锁的查询属于当前读,读取的是最新的数据记录;

3、间隙锁(Next-Key锁),可以防止幻读的发生

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的 索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁 (Next-Key锁)。
举例来说,假如emp表中只有101条记录,其empid的值分别是 1,2,…,100,101,下面的SQL:

Select * from  emp where empid > 100 for update;

是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使 用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需 要。有关其恢复和复制对锁机制的影响,以及不同隔离级别下InnoDB使用间隙锁的情况,在后续的章节中会做进一步介绍。

很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。

还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!下面这个例子假设emp表中只有101条记录,其empid的值分别是1,2,……,100,101。

具体内容可参考:https://www.cnblogs.com/leedaily/p/8378779.html

第二次补充:
MySQL 加锁处理分析 https://www.jianshu.com/p/e96e26c89869
MySQL加锁范围分析 https://cloud.tencent.com/developer/article/1148442
https://www.cnblogs.com/crazylqy/p/7821481.html

注:由上述3篇文章,可以得出一个简单的结论(只分析排他锁的情况):
数据库在使用next-key lock时
1、对于加排他锁(X锁),要区分是否使用了索引,没有的话,会进行表锁(插入时除外);使用了索引又要区分是否是唯一性索引,对于非唯一性索引还需要加 gap锁(间隙锁);在使用非唯一性索引时,间隙锁的范围不仅跟非唯一性索引有关,还跟关联的主键索引相关。
2、对于插入来说,除了对插入的主键索引加记录锁外(只锁一行),在插入前还会加一种锁,官方文档称它为insertion intention gap lock,也就是意向的gap锁。这个意向gap锁的作用就是预示着当多事务并发插入相同的gap空隙时,只要插入的记录不是gap间隙中的相同位置,则无需等待其他session就可完成,这样就使得insert操作无须加真正的gap lock。

一分钟深入Mysql的意向锁——《深究Mysql锁》https://blog.csdn.net/zcl_love_wx/article/details/82015281
在给索引加记录锁(也就是行锁)的时候,会自动加意向锁;意向锁是一种表锁,为了实现多粒度锁机制而被引入的。

MySQL innoDB 间隙锁产生的死锁问题 https://sq.163yun.com/blog/article/192340721236127744

InnoDB有三种行锁的算法:
1,Record Lock:是加在索引记录上的。
2,Gap Lock(间隙锁):对索引记录间的范围加锁,或者加在最后一个索引记录的前面或者后面
3,Next-Key Lock:前两种锁的结合,锁定一个范围,并且锁定记录本身,主要目的是解决幻读的问题。

间隙锁主要是防止幻象读,用在Repeated-Read(简称RR)隔离级别下。在Read-Commited(简称RC)下,一般没有间隙锁(有外键情况下例外,此处不考虑)。间隙锁还用于statement based replication

间隙锁有些副作用,如果要关闭,一是将会话隔离级别改到RC下,或者开启 innodb_locks_unsafe_for_binlog(默认是OFF)。
间隙锁(无论是S还是X)只会阻塞insert操作

mysql 在RR隔离级别下,默认使用Next-Key Lock。

关于mysql innodb间隙锁的一些思考 https://sq.163yun.com/blog/article/165933808891035648

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值