结论:不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。
但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
不可重复读重点在于update和delete,而幻读的重点在于insert。
悲观锁:它指的是对数据被外界修改持保守态度,读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。
乐观锁:大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
悲观锁和乐观锁在并发量低的时候,性能差不多,但是在并发量高的时候, 乐观锁的性能远远优于悲观锁。这也就是我们所说的长事务。
在RR的事务隔离级别下(有幻读问题):
快照读:普通的不加锁的select就是快照读。通过readview实现,可重复读级别时,整个事务的普通select都是使用同一个readview。其他事务产生的数据不会影响到这个快照。
当前读:读取最新版本已提交的数据。就算其他事务版本大于当前事务,但其他事务只要提交了的数据,当前事务使用当前读是可以读取到其他事务提交的数据的。而快照读则无法读取到其他新版事务提交的数据。
怎么解决呢?
一、隔离级别设置为Serializable串行执行,但资源消耗最大。
二、间隙锁,(间隙锁和行锁的合集称为 next-key lock)
next-key lock影响并发怎么办?参考(幻读在 InnoDB 中是被如何解决的? - 来份锅包肉 - 博客园)
最主要的是,间隙锁会有增加死锁出现的概率。
所以在业务不需要 RR 支持下,如果想提高并发率,可以将隔离级别设置成 RC ,并将 binlog 格式设置成 row。(为什么要把MySQL的binlog格式修改为row_javaxuexilu的博客-CSDN博客_binlog为row)
在RC的模式下,MVCC解决不了幻读和不可重复读