幻读
并发情况下,A事务读取了一条记录,此时B事务插入一条记录,A事务又读取,读到了两条数据,此时就造成了读取数据不一致,一般到这幻读通常说的是事务提交了,而且是指删除、插入带来的问题。
问题
mysql MVCC 多版本并发控制中我们介绍了什么是MVCC,MVCC会为每个事务生成一个某个时间点的快照数据,保证事务内可以避免每次读数据不一致的问题,是RC、RR隔离级别下用于提高数据库性能,避免频繁锁,读写分离实现的一种方式,既然读快照信息,那么是不是MVCC就可以避免幻读,因为事务已经有了快照数据,如果这样,那为啥RR、RC 隔离级别不能避免幻读?这个问题也有很多讨论,包括知乎上也有很多答案,本文也做一些思考和讨论,参考的文档在本文下方参考博客上。
快照读:snapshot read
MVCC针对每个事务都有一个快照数据,每次都从快照数据库读取,如果事务A内有这么一个逻辑:
- if sex =‘男’ 就执行sql:
update tb_user set type = 1 where id = 8;
因为每次都是快照读,那么每次读肯定都是男,如果在事务没commit 之前,事务B 执行结束了,它把sex改为了‘女‘,此时,你的条件不满足,但你因为读取快照信息,你也无法发现问题,所以你还是执行了sql,此时此刻,似乎也没有问题,但总体来说,这是错误的。所以当事务逻辑中有update的sql时,快照读是有问题的。
当前读:locking read
当前读是读取最新数据的一种读方式,不会从快照数据里读取,这样就可以读取到其他事务已经提交的数据,是特殊的读操作,插入/更新/删除操作属于当前读,需要加锁。
- select * from table where ? lock in share mode; (加S锁)
- select * from table where ? for update;(加X锁)
- insert into table values (…);(加X锁)
- update table set ? where ?;(加X锁)
- delete from table where ?;(加X锁)
所以MVCC为了解决快照读的问题引入了当前读,innodb 引擎实际执行的是当前读,但如果使用select for update,当前读就能看到每次读的数据就不一样了,就引起了幻读,你能看到其他事务insert的数据。
总结:快照读是没有幻读存在的,当前读才会出现幻读
幻读产生
select不会有phantom,读的是快照;可对于select for update(locking read)这种语句,被认为是写,如果:
- 事务A:select读快照(non-locking read)
- 事务B:insert 一条记录
- 事务A:select for update(locking read)
幻读就产生了,事务A两次读,数据数量变了。
如何解决幻读
很明显可重复读的隔离级别没有办法彻底的解决幻读的问题,如果我们的项目中需要解决幻读的话也有两个办法:
- 使用串行化读的隔离级别
- MVCC+next-key locks:next-key locks由record locks(索引加锁) 和 gap locks(间隙锁,每次锁住的不光是需要使用的数据,还会锁住这些数据附近的数据)
参考博客
MySQL的可重复读级别能解决幻读吗
既然MySQL中InnoDB使用MVCC,为什么REPEATABLE-READ不能消除幻读?
mysql在RR的隔离级别下,究竟是通过MVCC解决幻读的还是通过行锁的next key算法解决的?