- mysql中的RR隔离级别不会出现幻读。
- MVCC控制过程中,mysql会往每条数据附加三个列,分别是DB_TRX_ID(最后更新(insert/update/delete)数据的事务id与一个标志位标识该记录是否为删除记录)、DB_ROLL_PTR(回滚指针)与DB_ROW_ID(自增,用于标识数据的更新时间).
- 每个事务开始时系统会分配一个事务号,该事务号递增。
- MVCC(RR级别)中的读操作分为快照读(很可能是历史数据)以及当前读,其中当前读包括select * ... for update;insert;delete等,它读的是最新数据(由锁来实现),RR隔离级别实现的效果是对两种读都成立,即快照读中不会有幻读以及不可重复读,在当前读也不会出现幻读或不可重复读。
- 快照读实现RR效果
快照读即是select * from table where condition;没有for update等语句。
在InnoDB中通过一个ReadView的数据结构来实现快照。该数据结构中有以下数据,max_tx_id、min_tx_id、exclude_tx_ids.该数据结构在一个事务中的第一次SnapShot调用中创建,并在该事务中不再改变。其中max_tx_id一般取快照读时,系统的事务号,所有大于该事务号的事务数据,都是在当前快照后创建的,对于当前快照应该是不可见的。min_tx_id,指示了所有的可用数据的事务id的最大值,其一般取活动事务(未提交)的最小值,exclude_tx_ids指向所有的不可见事务id,一般是创建SnapShot时的所有活动事务。以下对update/delete/insert分别介绍其执行规则,说明为什么ReadView能满足RR效果。
//以下操作都会在获得锁后立即执行,不必等到事务提交。
//insert:插入一条数据,将DB_TRX_ID设置为当前事务id,DB_ROLL_PTR指向上一条数据。
//delete:将DB_TRX_ID设置为当前事务id并标记为删除(新增一条),并设置DB_ROLL_PTR。
//update:先把原本数据的DB_TRX_ID设置为当前事务id并标记为删除(新增一条),然后执行一个insert操作。
//select,通过ReadView可以很快判断出一条数据的DB_TRX_ID所对应的事务对于当前快照是否可见,然后利用DB_ROW_ID,往回查找所有可见数据中的最新数据。
eg:假设事务t2在事务t1快照之后启动,所以
|val1|val2|DB_TRX_ID|DB_ROW_ID|
初始数据:|-|-|1|1|
t1执行SnapShot,标志t2_id不可见。
t2更新数据,数据记录变更为以下
|-|-|10|3|
|-|-|10d|2|
|-|-|1|1|
t1再次读取时,由于知道DB_TRX_ID为10的事务数据是不可见的,所以直接略过两条,读取了|-|-|1|1|即此时只有该数据是有效数据。当然如果读到的最新有效数据是一条删除数据(d),则此次读取无结果。由于insert使用了不可见事务的id,所以会被忽略,所以无幻读。
- 当前读的RR效果实现则由锁进行保证,详细可见该博客。
- InnoDB的RR以及RC级别,差别就在于RR中存在着GAP锁,这在阻止某些insert语句的同时,也阻止了update与delete语句(update本质上也是insert).
- InnoDB的锁操作决定了,其在RC/RR隔离等级下都不可能出现第一类更新丢失。但是都存在第二类更新丢失(除非使用当前读(for update)读取数据),但更好的方法应该是从程序中使用乐观版本控制(添加version列)。