首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

自己动手实践理解数据库REPEATABLE READ&Next-Key Lock

[REPEATABLE READ]

首先设置数据库隔离级别为可重复读(REPEATABLE READ):

[REPEATABLE READ]能解决的问题之一

[REPEATABLE READ]隔离级别解决了不可重复读的问题,一个事务中多次读取不会出现不同的结果,保证了可重复读。还是上一篇中模拟不可重复读的例子:

事务1

事务2

事务1先于事务2执行。

事务1的执行信息

事务2的执行信息

执行结果

结论:可重复读[REPEATABLE READ]隔离级别解决了不可重复读的问题。

分析:可重复读[REPEATABLE READ]隔离级别能解决不可重复读根本原因其实就是前文讲过的read view的生成机制和[READ COMMITTED]不同。[READ COMMITTED]:只要是当前语句执行前已经提交的数据都是可见的。[REPEATABLE READ]:只要是当前事务执行前已经提交的数据都是可见的。在[REPEATABLE READ]的隔离级别下,创建事务的时候,就生成了当前的global read view,一直维持到事务结束。这样就能实现可重复读。

从事务1的执行信息中的[SQL 3]我们可以得知,[REPEATABLE READ]隔离级别读操作也是不加锁的。因为如果读需要加S锁的话,是在事务结束时释放S锁的。那么事务1[SQL 3]进行更新操作申请X锁的时候便会等待事务2的S锁释放。现实并不是。

我们知道,MySql的InnoDB引擎是通过MVCC的方式在保证数据的安全性的同时,实现了读的非阻塞。MVCC模式需要额外的存储空间,需要做更多的行检查工作;但是保证了读操作不用加锁,提升了性能,是一种典型的牺牲空间换取时间思想的实现。需要注意的是,MVCC只在[READ COMMITTED]和[REPEATABLE READ]两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容,因为[READ UNCOMMITTED]总是读取最新的数据行,而不是符合当前事务版本的数据行。而[SERIALIZABLE]则会对所有读取的行都加锁。

通过亲自实践模拟分析[READ COMMITTED]和[REPEATABLE READ]两个隔离级别的工作机制,我们也能深刻的体会到各个数据库引擎实现各种隔离级别的方式并不是和标准sql中的封锁协议定义一一对应的。

[REPEATABLE READ]能解决的问题之二

幻读其实是不可重复读的一种特殊情况。不可重复读是对数据的修改更新产生的;而幻读是插入或删除数据产生的。所谓的幻读有2种情况,一个事物之前读的时候,读到一条记录,再读发现记录没有了,被其它事务删了,另外一种是之前读的时候记录不存在,再读发现又有这条记录,其它事物插入了一条记录。

事务1

事务2

执行结果

1.预期结果

2.实际结果

事务1中并没有读取到事务2新插入的数据,并没有发生幻读现象。这有点出乎我的意料,难道Mysql[REPEATABLE READ]隔离级别能解决幻读问题?按照封锁协议定义,三级封锁协议是解决不了幻读的问题的。只有最强封锁协议,读和写都对整个表加锁,才能解决幻读的问题。但是这样做相当于所有的操作串行化,数据库支持并发的能力会变得极差。所以Mysql的InnoDB引擎通过自己的方式在[REPEATABLE READ]隔离级别上解决了幻读的问题,下面我们探究一下InnoDB引擎是如何解决幻读问题的。

分析:InnoDB有三种行锁的算法:1.Record Lock:单个行记录上的锁。2.Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。3.Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。主要目的是解决幻读的问题。

在[REPEATABLE READ]级别下,如果查询条件能使用上唯一索引,或者是一个唯一的查询条件,那么仅加行锁(通过唯一的查询条件查询唯一行,当然不会出现幻读的现象);如果是一个范围查询,那么就会给这个范围加上 Gap锁或者 Next-Key锁 (行锁+Gap锁)。理论上不会发生幻读

验证一下Gap Lock和Next-Key Lock的存在

我们可以通过自己操作来验证一下Gap Lock和Next-Key Lock的存在。首先我们需要给state字段加上索引。然后准备几条数据,如下图:

事务1

事务2

因为InnoDB对于行的查询都是采用了Next-Key Lock的算法,锁定的不是单个值,而是一个范围(GAP)。上面索引值有1,3,5,8,其记录的GAP的区间如下:(-∞,1],(1,3],(3,5],(5,8],(8,+∞)。是一个左开右闭的空间。需要注意的是,InnoDB存储引擎还会对辅助索引下一个键值加上Gap Lock。事务1语句①锁定的范围是(1,3],下个键值范围是(3,5],所以插入1~4之间的值的时候都会被锁定,要求等待,等待超过一定时间便会进行超时处理(Mysql默认的超时时间为50秒)。插入非这个范围内的值都正常。

[REPEATABLE READ]读到底加不加锁?

当我理解了[REPEATABLE READ]隔离级别是如何解决幻读问题时,随即产生了另一个疑问。[READ COMMITED]和[REPEATABLE READ]通过MVCC的方式避免了读操作加锁的问题,但是[REPEATABLE READ]又为了解决幻读的问题加Gap Lock或Next-Key Lock。那么问题来了,[REPEATABLE READ]读到底加不加锁?我对这个问题是百思不得其解,直到读到了这篇文章才算理解了一些。

我们可以思考一下如果InnoDB对普通的查询也加了锁,那和序列化(SERIALIZABLE)的区别又在哪里呢?我的理解是InnoDB提供了Next-Key Lock,但需要应用自己去加锁。这里又涉及到一致性读(快照读)和当前读。如果我们选择一致性读,也就是MVCC的模式,读就不需要加锁,读到的数据是通过Read View控制的。如果我们选择当前读,读是需要加锁的,也就是Next-Key Lock,其他的写操作需要等待Next-Key Lock释放才可写入,这种方式读取的数据是实时的。

一致性读很好理解,读不加锁,不堵塞读。当前读对读加锁可能比较难理解,我们可以通过一个例子来理解一下:

执行结果

结论MVCC是实现的是快照读,Next-Key Lock是对当前读。MySQL InnoDB的可重复读并不保证避免幻读,需要应用使用加锁读来保证,而这个加锁读使用到的机制就是Next-Key Lock

-----END-----

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180512G0AT8A00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券