前言

之前了解过MySQL 中MVCC的相关知识,但只是知道概念,没有在实际中好好做过验证和总结,今天来从实际出发来探讨MVCC的相关知识,理论结合实践,从而对一些原理有更深的认识。

准备数据

准备一张user表,表结构信息如下:

表中有两个字段,id为主键,还有个普通字段username。

当前MySQL默认的隔离级别是可重复读:

实验

实验一

我们进行如下实验,开启两个cmd窗口,两个连接,按顺序依次执行如下命令:

在第⑤步中我们查出来了4条记录,id分别为1——4。在第⑥步的时候,我们insert又插入了一条id为4的记录。这是怎么回事呢?

这里我们先说下对幻读的理解:

幻读错误的理解:说幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 得到 11 条记录。这其实并不是幻读,这是不可重复读的一种,只会在 R-U R-C 级别下出现,而在 mysql 默认的 RR 隔离级别是不会出现的。

幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读

上面的情况就属于幻读的一种表现。

实验二

再看下面的一个例子:

在同一个事务里,select * from userselect * from user for update两种情况的读取结果不一样。

分析

为什么会有这种情况呢?这就要从MVCC说起了。

关于MVCC的基础知识,可以看 mysql事务并发控制之MVCC 这篇文章。

这里需要明确两个概念,当前读与快照读。

快照读:读取的可能是版本链中的任意一个版本的记录。

简单的select操作(不包括select...lock in share mode,select...for update) 使用的是快照读。

当前读:读取的是当前数据的最新记录。

1
2
3
4
5
6
7
8
9
select ... lock in share mode

select ... for update

insert

update

delete

以上这些操作都使用的是当前读。

现在我们来回答我们上面实验中出现的现象。在MVCC的实现下,是区分当前读和快照读的。

在实验二当中,⑤和⑥两个步骤的结果表明,这种情况连可重复读的隔离级别都没有达到。所以在MVCC的实现下,看隔离级别需要分别来看,要么都是使用快照读,要么从当前读的角度来看,不能两者混着来用。什么意思呢?意思就是,要么都用⑤的这种查询方式,都使用的是快照读,要么都使用⑥的查询方式,都使用当前读,不能⑤和⑥同时用来看隔离级别。

问题

我们来看一个问题:

为什么提到幻读时,都是说其他事务插入数据引发幻读,而没有提及其他事务删除数据引发幻读?

我们来分析下这个问题,说起幻读,我们一般是在可重复读的隔离级别下谈论,因为其他的隔离级别比如读已提交、读未提交连可重复读都满足不了,更别说幻读了。

在可重复读的隔离级别下,是能够确保两次读都能读取到一样的数据的(在快照读和当前读不混用的前提下),所以其他事务删除数据是不影响的。也就是说并不会引起幻读。

我们做实验来印证:

当前读实验

我们首先使用当前读来做实验,如下:

①使用当前读,③删除id=6的数据,③在等待①的锁释放,①在长时间没有提交事务的情况下,导致③等待锁超时。

可以看到,当前读的情况下MySQL使用了加锁的机制来保证数据的可重复读,当前事务加锁后,其他事务无法删除。

快照读实验

再使用快照读来做实验,如下①到⑥:

可以看到,在可重复读的隔离级别下①③⑤都是快照读,读取的结果都是一致的。符合可重复读的隔离级别。

分析

我们再来回到问题本身

为什么提到幻读时,都是说其他事务插入数据引发幻读,而没有提及其他事务删除数据引发幻读?

我们细细品下这个问题,我们在上面的实验中证明了在RR隔离级别下删除数据并不会引发幻读。但是插入数据会不会引发幻读,这个我们需要继续分析。

我们从读写锁的角度来讨论这个问题。

1
2
3
* 读锁(S锁):共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响。

* 写锁(X锁):排他锁,当前写操作没有完成前,它会阻断其他写锁和读锁。

我们分几种情况来讨论读写锁:

如果事务一进行读操作,那么给该记录加读锁,事务二进行读操作,可以并行,互补影响;

如果事务一进行读操作,那么给该记录加读锁,事务二进行写操作,如果是对同一条记录进行写操作,比如更新或删除,则无法并行,必须等事务一释放了读锁之后才可,可如果是insert操作,那么事务一的读锁并不会阻碍事务二的新增操作,所以两个事务仍然可以并行。所以在这种情况下可以回答我们上面为什么提及幻读会考虑插入数据而不考虑删除数据?的问题了。

总结

通过现象看本质,我们从两个实验出发,来一步步分析幻读的问题,对MVCC的相关知识有了更深的认识。