浅析MySQL数据库在InnoDB存储引擎下的READ COMMITTED与REPEATABLE READ隔离级别以及不可重复读与幻读现象

一、InnoDB存储引擎下的一致性非锁定读与一致性锁定读

MySQL在InnoDB引擎下对于READ COMMITTED与REPEATABLE READ这两个隔离级别均采用的是一致性非锁定读,即所谓的多版本并发控制协议(MVCC),这样增加了并发性能。这两种隔离级别下默认的读操作都是读快照,但是读取的快照版本不一样,对于READ COMMITTED读取的是最新的快照版本,所以一个事务对数据的修改在其提交后对另一个事务是可见的,正是因为其读取的是最新版的快照,但是对于REPEATABLE READ隔离级别下事务读取的快照永远是事务刚开启的那个旧的快照版本,这样就不会读取到另一个事务新提交的数据,不会引起不可重复读与幻读现象,至于不可重复读与幻读待会会解释。

而MySQL可以显示的采用如下两种方式进行一致性锁定读,分别加的是共享锁(S)与排它锁(X):

SELECT * FROM table LOCK IN SHARED MODE

SELECT * FROM table FOR UPDATE

二、什么是不可重复读与幻读

不可重复读与幻读都是指同一个事务在两次读取记录时出现不一致的情况,造成不一致的原因是其间另一个事务对查找的记录进行了修改。但是不可重复读一般是针对同一条记录而言,幻读是针对不同的记录而言,一般是范围查询。

三、READ COMMITTED隔离级别下的不可重复读现象(采用一致性非锁定读)

1、设置READ COMMITTED隔离级别,开启事务A,查询表t中a为1的记录

2、设置READ COMMITTED隔离级别,开启事务B,更改a等于1的记录

3、在事务A中再次查询a为1的记录得到不同的结果

上面就是READ COMMITTED隔离级别下的所谓的不可重复读问题。

四、在READ COMMITTED隔离级别下采用一致性锁定读解决不可重复读现象,但存在幻读现象

对于上面出现的不可重复读问题用户可以人为的采用一致性锁定读来解决,具体看下面:

1、还是在READ COMMITTED隔离级别下面,事务A采用一致性锁定读来获取a为1的记录

2、设置READ COMMITTED隔离级别,开启事务B,更改a等于1的记录

此时发现修改不了,出现了锁等待超时。

3、在事务A中再次查询a为1的记录得到相同的结果

上面就是采用一致性锁定读解决了READ COMMITTED 隔离级别下出现的不可重复读问题,但是这解决不了幻读问题,具体事例见下面分析。

1、还是在READ COMMITTED隔离级别下面,事务A采用一致性锁定读来获取a在1到5这个范围内的记录

2、设置READ COMMITTED隔离级别,开启事务B,插入新的记录2

3、在事务A中再次查询a在1到5这个范围内的记录,发现多了一条记录,就好像幻读了一样

上面能够解决不可重复读不能解决幻读的原因:对于READ COMMITTED隔离级别,在进行范围读时,由于唯一性约束,系统采用的是record lock,也就是只对记录加锁,所以不能更改记录,即能够解决不可重复读的问题。但是记录2不在原本的记录当中,是可以进行插入的,也就是没法解决幻读问题。

五、REPEATABLE READ隔离级别下采用多版本并发控制解决不可重复读与幻读(一致性非锁定读)

1、在REPEATABLE READ隔离级别下面,事务A采用一致性非锁定读来获取a为1的记录

2、设置REPEATABLE READ隔离级别,开启事务B,更改a为1的记录使其值为2并提交

3、在事务A中再次查询a为1的记录,发现与之前结果相同

上面的情况就是在一致性非锁定读的情况下REPEATABLE READ隔离级别解决不可重复读现象,其采用的是多版本并发控制协议(MVCC),读取的永远都是事务刚开始时的快照,也就是历史数据,所以不会看到另一个事务已提交了的数据。

上面MVCC同样也解决了幻读的问题:

1、在REPEATABLE READ隔离级别下面,事务A采用一致性非锁定读来获取a在1到5之间的记录

2、设置REPEATABLE READ隔离级别,开启事务B,插入a为2的新纪录并提交

3、在事务A中再次查询a在1到5之间的记录,发现与之前结果相同

上面解决幻读也是通过多版本并发控制实现的,即读取的是快照历史数据,对另一个事务提交的数据不可见。

六、REPEATABLE READ隔离级别下采用一致性锁定读解决不可重复读与幻读

1、在REPEATABLE READ隔离级别下面,事务A采用一致性锁定读来获取a为1的记录

2、设置REPEATABLE READ隔离级别,开启事务B,更改a为1的记录使其值为2并提交

可以看到出现了锁等待超时,记录修改不能成功,所以还是原来的记录

3、在事务A中再次查询a为1的记录,发现与之前结果相同

上面就是采用一致性锁定读中的锁解决的不可重复读,在获取记录1时加了一个共享锁S,此时想修改该记录,就必须在该记录上申请排它锁X,而排它锁X与共享锁S是不兼容的,所以出现锁等待,事务A一直没有提交,最后事务B就会锁等待超时。

此时采用加锁的方式仍然可以解决幻读问题:

1、在REPEATABLE READ隔离级别下面,事务A采用一致性非锁定读来获取a在1到5之间的记录

2、设置REPEATABLE READ隔离级别,开启事务B,插入a为2的新纪录并提交

这个地方就和上面在READ COMMITTED隔离级别下有所不同了,上面READ COMMITTED隔离级别下是可以插入记录2的,在这里却出现了锁等待,可见InnoDB存储引擎对这两种隔离级别下采取的锁算法是不一样的,READ COMMITTED隔离级别下采取的是record lock,即仅锁住查询出来的存在的记录,而这里是锁住了一个范围,采取的是next-key lock,锁住了负无穷到正无穷的范围,这里就算插入的记录2在这个范围内,会出现锁阻塞。

3、在事务A中再次查询a在1到5之间的记录,发现与之前结果相同

同样在REPEATABLE READ 隔离级别下也能通过一致性锁定读(next-key lock)解决幻读问题。

总结:在REPEATABLE READ 隔离级别下,通过next-key lock可以解决不可重复读与幻读问题,但是在READ COMMITTED隔离级别下系统加的是record lock只能解决不可重复读问题,解决不了幻读问题。

猜你喜欢

转载自blog.csdn.net/Wenlong_L/article/details/84074761