上文中已经总结了Mysql是如何利用MVCC实现四个隔离级别的,但是对于幻读问题,MVCC是无能为力的。在RR隔离级别下是使用Next-Key Lock 来解决幻读的.
Next-Key Lock
行锁 : 锁定的是当前行的索引,如果没有索引会默认锁定隐式索引
间隙锁 : 锁定的是一个不存在的间隙
Next-Key Lock : 行锁+间隙锁的实现,也是解决幻读问题的关键
原理
//事务A 事务B
BEGIN; BEGIN;
SELECT COUNT(*) FROM ttt;
INSERT INTO ttt values(2,'aaaa');
SELECT COUNT(*) FROM ttt;
显而易见,在这样的情况下,事务A的两次查询结果必然不一致。那么如何解决这个问题,实际上很简单。我们只需要保证一个事务查询的区间在查询过程中不能够被操作即可。
通俗来讲,就是为事务A实现一个范围锁,这个范围就是该区间。
例如执行SELECT * FROM table WHERE id BETWEEN 1 AND 200,那么我们只需要保证在这个区间查找时,别的事务不能够对该区间进行增添删除操作即可,这里就利用到了我们上面所提到的间隙锁。
间隙锁的本质是锁住一个目前并不存在的数据,假设我们在id[100,200]这个区间中只有一个id=150的数据,但是对这个区间添加间隙锁,就能够将id=[100,150)(150,200]的空间全部锁住,也就是说,这时候只能够操作id=150的行,而其他行已经被锁住。
全表加锁的情况
//事务A 事务B
BEGIN; BEGIN;
SELECT COUNT(*) FROM ttt;
INSERT INTO ttt values(2,'aaaa');
SELECT COUNT(*) FROM ttt;
部分数据加锁
//事务A 事务B
BEGIN; BEGIN;
select * from ttt WHERE id BETWEEN 1 AND 200;
INSERT INTO ttt values(250,'aaaa');
select * from ttt ;
可以看到当事务A使用COUNT(*)操作时,会锁止整个表,因为这个语句涉及的区间是整个空间,而如果是类似于BETWEEN ... AND ...这类语句,则只会锁住表内一段空间,如果去操作其他的空间,就不会发生阻塞.
总结
从上边的描述中我们可以看出来,所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复这个ReadView就好了。