从一个问题开始
前提:在可重复读事务隔离级别下,事务的开始都是从开始执行sql开始的。一般的begin/start transaction并不能立刻开启事务。
我们可以使用 start transaction with consistent snapshot;来立刻开启一个事务。
现在有表和数据:
CREATE TABLE `t` (`id` int(11) NOT NULL,`k` int(11) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);
sessionA | sessionB | sessionC |
start transaction with consistent snapshot; | ||
start transaction with consistent snapshot; | ||
update t set k = k+1 where id=1; | ||
update t set k = k+1 where id=1; select * from t where id =1; | ||
select * from t where id=1; commit; | 空位C | |
commit; |
sessionC没有显示开启事务,说明它本身就是一个事务。更新完后自动提交。
结果:sessionA查到k的结果为:1,sessionB查到k的结果为3.
sessionA很好解释,可重复读下,开启事务查到的结果是一致的,所以结果是1。Mysql是是如何实现的呢?
这要说到Mysql的视图分为2种,一种是view,是一个虚拟表,真是存在,可以当做表查询;另一种就是MVCC用到的一致性读视图。它不是真实存在的,而是通过数据更新的不同的trx_id去根据不同版本找到所要的值得。
每个事务都会唯一分配一个自增trx_id,当对数据做更改后,数据会保存当前更改的trx_id。而当前事务开启时,会保存一个trx_id数组,数组内容为当前所有未提交事务trx_id集合和最大trx_id+1的值。其中最小的值叫低水位,最大trx_id+1叫高水位。当前事务能否读到一条数据修改,取决对当前事务trx_id和trx_id数组的比较。
低水位以下 | trx_id数组 | 高水位以上 |
1、如果查询的数据的更新trx_id在低水位以下,表示是已提交后者当前事务提交的,是可见的
2、如果处在trx_id未提交数组区间中
1)、如果在数组中,表示还未提交,不可见
2)、如果不在数组中,表示已提交,可见
3、如果在高水位以上,则是不可见的
看上面的图,如果当前一条数据分别被事务A,B,C操作,trx_id分别为90,101,100,现在k对应的trx_id=89。
此时A数组对应的trx_id数组为[90,91],B对应的trx_id为[90,100,101,102],C对应的trx_id是[90,100,101]。
由于A读数据,发现此时k=3,trx_id为B的100,100落于A的高水位上,不可见,需要回退,回退到C对应的trx_id=100,也落于高水位上,继续回滚到trx_id=89,落于低水位下,可见。得到K=1的值。
总结:
1、版本未提交,不可见
2、版本已提交,但是在事务开启视图创建之后提交,不可见
3、版本已提交,在事务开启,视图创建之前提交,可见
这就是可重复读隔离级别下的MVCC机制可读性的原理。
但是对于sessionB来说,为什么值是3?
这是因为Mysql在更新时,都需要先读后写,这个读都是读最新的值,就是“当前读”。否则mysql就会丢失C事务的更新操作了。
所以B先读C更新的k=2,再加1,所以再查询得到k=3。
但是如果将C事务提交放在绿色区域(空位C),此时B事务再去修改,由于两阶段锁的作用,C事务还未释放锁,B拿不到锁,只能等待,直到C提交。
到这里,我们就把MVCC一致性读,当前度和两阶段锁联系起来了。
现在,如果我们修改数据库的事务隔离级别为读已提交。
由于with consistent snapshot 是即时创建可重复读视图,所以读已提交下,该语句等同于begin。所以A查到的值为2,B为3。