在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
对于会对数据修改的操作(update、insert、delete)都是采用当前读的模式。
当执行select操作是innodb默认会执行快照读,会记录下这次select后的结果,之后select 的时候就会返回这次快照的数据,即使其他事务提交了不会影响当前select的数据,这就实现了可重复读了。快照的生成当在第一次执行select的时候,也就是说假设当A开启了事务,然后没有执行任何操作,这时候B insert了一条数据然后commit,这时候A执行 select,那么返回的数据中就会有B添加的那条数据。之后无论再有其他事务commit都没有关系,因为快照已经生成了,后面的select都是根据快照来的。
使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。
怎么判断快照读和当前读:
快照读:最简单的select操作,属于快照读,不加锁
select * from table where id = 1;
当前读:特殊的读与增删改操作,属于当前读,会读取数据库原本的数据,加锁
select * from table where xxx lock in share mode; (共享锁)
select * from table where xxx for update;
insert into table values()
update table set xxx where xxx
delete form table where xxx
幻读产生的原因(只有当前读才能出现幻读)
产生的前提条件:必须是InnoDB引擎在可重复读隔离级别,使用当前读时。
幻读的表现:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行(表示这时候有新行的insert(插入))。
tips:为什么必须是insert呢?
因为delete和update产生的是不可重复读(与脏读幻读并列的三大问题之一),通过行锁锁定之后,就可以避免对当前行的修改影响数据读取了。
但是行锁只能锁住行,即使把所有的行记录都上锁,也阻止不了新插入的记录。
两点需要说明:
1、在可重复读隔离级别下,普通查询是快照读,是不会看到别的事务插入的数据的,幻读只在当前读下才会出现。
2、幻读专指新插入的行,读到原本存在行的更新结果不算(只有读到新插入的数据才算幻读,读到原数据的修改不算)。因为当前读的作用就是能读到所有已经提交记录的最新值。
如何解决幻读
将两行记录间的空隙加上锁,阻止新记录的插入;这个锁称为间隙锁。
间隙锁与间隙锁之间没有冲突关系。跟间隙锁存在冲突关系的,是往这个间隙中插入一个记录这个操作。
脏读:就是读到了还没commit的数据(因为事务没有提交就可能回滚),一旦回滚就是脏读,在读未提交这个隔离级别容易出现