【MySQL】面试题之:MVCC能否解决幻读?

       “幻读”指,同一个事务里面连续执行两次同样的sql语句,可能导致不同结果的问题,第二次sql语句可能会返回之前不存在的行。

先给出结论:不能笼统的说能不能解决,因为有的情况下可以解决,但是有的情况下解决不了。


可以解决的情况

mysql里面实际上有两种读,一种是“快照读”,比如我们使用select进行查询,就是快照读,在“快照读"的情况下是可以解决“幻读”的问题的。使用的就是MVCC,具体来说如下图,几个事务并发执行:

可以看到,尽管别的事务已经提交插入和更新,但是事务A的select读取的还是一样的。具体就是mvcc利用历史版本信息(快照)来控制他能读取的数据的范围。具体的可以看看我的关于MVCC浅析的文章。


另外一种读是:“当前读”。对于会对数据修改的操作(update、insert、delete)都是采用当前读的模式,此外,下面两个语句也是当前读:

1、select * from table where ? lock in share mode; (加共享锁)

2、select * from table where ? for update; (加排它锁)

因此总结一下,下面几个语句都是当前读,都会读取最新的快照数据,都会加锁(除了第一个加共享锁,其他都是互斥锁):

select * from table where ? lock in share mode; 
select * from table where ? for update; 
insert; 
update; 
delete;

在执行这几个操作时会读取最新的记录,即使是别的事务提交的数据也可以查询到。比如要update一条记录,但是在另一个事务中已经delete掉这条数据并且commit了,如果update就会产生冲突,所以在update的时候需要知道最新的数据。读取的是最新的数据,并且需要加锁(排它锁或者共享锁)

举个例子,下面是在可重复读级别下,事务1在update后,对该数据加锁,事务B无法插入新的数据,这样事务A在update前后数据保持一致,避免了幻读,可以明确的是,update锁的肯定不只是已查询到的几条数据,因为这样无法阻止insert,有同学会说,那就是锁住了整张表呗,其实不是,其实这里的锁,是next-key locking(就是一个行锁+范围锁)实现的.行锁不必说,就是更新的时候锁住这一行,这样别的事务就不能同时进行修改操作了。范围锁(gap lock)锁则是防止插入。


什么是next key lock?

所谓的next key lock就是一个行锁(record lock)+范围锁(gap lock),比如某一个辅助索引(比如上面的class_id),如果它有1,3,5这几个值,那么当我们使用next key lock的锁住class_id=1的时候,实际上锁住了(-无穷,1],或者锁住class_id=3的时候,实际上锁住的是(1,3],也就是一个左开右闭的区间。如果此时别的事务要在这个区间内插入数据,就会被阻塞住。这个锁一直到事务提交才会释放。因此,即使出现了上面图片里面这种情况,也可以保证前后两次去读的内容一致,因为对这个辅助索引上的锁是:“next key lock”,他会锁住一个区间。

 但是注意,对于可重复读默认使用的就是next key lock,但是对于“唯一索引”,比如主键的索引,next key lock会降级成行锁,而不会锁住一个区间。因此,如果上面的事务1的update使用的是主键,事务2也使用主键进行插入,那么实际上事务2根本不会被阻塞,可以立即插入并返回。而对于非唯一索引,next key lock则不会降级。


什么情况MVCC也会出现幻读?

下面这样的情况:

1.a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意操作),

2.a事务再select出来的结果在MVCC下还和第一次select一样,

3.接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),

4.a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了.

上面这样,事务2提交之后,事务1再次执行update,因为这个是当前读,他会读取最新的数据,包括别的事务已经提交的,所以就会导致此时前后读取的数据不一致,出现幻读。

参考:

https://www.cnblogs.com/CoderAyu/p/11525408.html

猜你喜欢

转载自blog.csdn.net/qq_35590091/article/details/107734005
今日推荐