1、RC(读已提交)和RR(可重复读)级别都用到了MVCC来进行不加锁的读,但是为什么RR级别可以解决幻读,对于RC级别不行?
MVCC即多版本并发控制,能够保证多个读请求之间不会进行阻塞,根据事务隔离级别和事务id来确定当前事务能够查询到的数据的版本,对于每行记录来说,可能会存在多个版本,而这些版本会使用链表进行关联起来,从而控制一个事务能够查询到的数据的版本。
原因:两种隔离界别下的核心处理逻辑就是判断所有版本中哪个版本时当前事务可见的处理。针对这个问题InnoDB在设计上增加了ReadView的设计,ReadView中主要包含当前系统中还有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为m_ids。
以上内容是对于 RR 级别来说,而对于 RC 级别,其实整个过程几乎一样,**唯一不同的是生成 ReadView 的时机,RR 级别只在事务开始时生成一次,之后一直使用该 ReadView**。而 RC 级别则在每次 select 时,都会生成一个 ReadView。
简易版的说:
当前活跃的事务列表集合,从小到大的顺序排列,在这个集合中的事务列表说明并未提交,对于本事务来说是不可见的,比该事件列表最大的id还要大,说明本事务执行时,修改记录的事务还未开始,所以比该事件列表最大的id还要大的,那么就是不可见的。
在RR级别下,事务执行第一个 select 语句的时候,会生成一个当前时间点的事务快照 ReadView,主要包含以下几个属性:
trx_ids:生成 ReadView 时当前系统中活跃的事务 Id 列表,活跃的事务 Id 列表从小到大的顺序排列,就是还未执行事务提交的。
creator_trx_id:生成该 ReadView 的事务的事务 Id。
在访问某条记录时,会根据该事务中的事务快照 ReadView进行判断:
- 行记录中的事务id是否为该事务本身的id,相同则说明自己创建的,可见
- 行记录中的事务id比事务快照 ReadView中最小的还小,表明该记录为之前提交的,能够被当前事务访问。
- 行记录中的事务id比事务快照 ReadView中最大的事务id还要大,表明该记录为之后提交的,不能被当前事务访问
- 行记录中的事务id在活跃的事务 Id 列表之间,那么就使用二分,判断是否能在活跃集合中找到该事务id,能找到,说明该事务快照时,还未提交,本事务无法访问,如不在活跃事务集合中,说明已提交,可以访问
在进行判断时,首先会拿记录的最新版本来比较,如果该版本无法被当前事务看到,则通过记录的DB_ROLL_PTR找到上一个版本,重新进行比较,直到找到一个能被当前事务看到的版本。
2、Mysql的可重复读级别能解决幻读吗?
A:不能完全解决,只能解决读的。更新的不能。
CREATE TABLE `dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8
insert into dept(name) values("后勤部")
| 事务 1 | 事务 2 | | begin | begin | | —- | —- | | select from dept | | | - | insert into dept(name) values(“研发部”) | | - | commit | | select from dept | | | commit | |
根据上面的流程执行,预期来说应该是事务1的第一条select查询出一条数据,第二个select查询出两条数据(包含事务2提交的数据)。
但是实际测试中发现第二条select实际上也只是查询处理了一条数据。
| 事务 1 | 事务 2 | | begin | begin | | —- | —- | | select * from dept | | | - | insert into dept(name) values(“研发部”) | | - | commit | | update dept set name=“销售部”(工作中如果不想被辞退一定要写where条件) | | | commit | |
根据上面的结果我们期望的结果是这样的:
id name
1 财务部
2 研发部
本来我们希望得到的结果只是第一条数据的部门改为了财务,但是结果确实两条数据都被修改了,这种结果告诉我们其实在Mysql可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决时不彻底的。
有个问题说明下:
在测试过程中最开始我以为使用begin
语句就是开始一个事务了,所以在上面第二次测试中因为先开始的事务1,结果在事务1中却查到了事务2新增的数据,当时认为这和前面MVCC中的select的规则不一致了,所以做了如下测试:
SELECT * FROM information_schema.INNODB_TRX //用于查询当前正在执行中的事务
可以看到如果只是执行begin语句实际上并没有开启一个事务。
下面在begin后添加一条select语句:
所以要明白实际上是对数据进行了增删改查等操作后才开启了一个事务。