前言
我们在前文的事务和前文的mysql语句执行流程中都谈到了视图这个概念,其实Mysql有两个视图:
1.view,指查询语句过程中定义的虚拟表
2.指innodb为实现MVCC时使用到的一致性视图,用于支持可重复读和读提交隔离级别的实现。
复习
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);

很明显A因为视图的关系读到k=1,B因为更新造成的当前读读到3
视图(基于可重复读分析)
视图相当于redis的RDB(快照),innodb每个事务都有唯一的事务id(trx_id),是按照顺序严格递增的
每次事务更新都会记录回滚日志,生成一个新的数据版本(把trx_id赋值给这个数据版本的事务id,记为row_trx_id),旧的数据版本要保留并在新的版本中能够拿到(数据的版本号不是递增的。)在上文锁中有提及,查看information_schema库中的innodb_trx表获取
先开始的事务获取的事务ID总是小于后开启的事务ID。 只读事务的ID和非只读事务的ID是有些区别的。前者是一个很大的数,后者是一个从1自增的数值。

虚线的三箭头为回滚日志undo log
事务启动时,能够看到已提交的事务结果,执行期间,其他的事务更新对其不可见。
启动时分配一个数据版本,如果一个数据版本是启动后生成就不认,找它的上一个版本(依据undo log),当然自己更新的还是认的(前提是更新成功,修改了数据)
没更新成功的例子:表t有id和c字段,c的值和id值相同
//sessionA执行
begin;
select * from t;
//sessionB
update t set c=c+1;
//sessionA执行
update t set c=0 where id=c;
select * from t ; //结果与第一次相同
//虽然是当前读但是,这个点已经没有id=c的字段了,这并没有生成新的版本数据
innodb为每个事务构造了一个数组,用来保存启动时所有启动了但未提交的事务ID
数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位
也就是说比低水位小说明一定是提交了的事务(数据版本可见),比高水位高一定是之后才开始的事务(数据版本不可见),介于之间的包含了未提交的事务和晚于未提交事务开启的事务但是已提交(这个不会出现在数组里)
当介于低水位和高水位之间时,若row_trx_id在数组中,表示这个数据版本是还没提交的事务生成,不可见;反之表示是已经提交的事务生成可见

如上图,当一个事务的低水位为18时row_trx_id的值小于18的都可见,当它访问这行数据时,从v4=25的数据版本的回滚日志U3得到V3=11 所以该事务的行值为11是可见。 回到刚开始的实验

1.假设A开始前系统只有一个活跃trx_id=99,
2.假设ABC的trx_id分别为100、101、102(innodb引擎默认一条语句开启一个事务,参考前文mysql语句执行流程日志的二阶段提交),
3.假设三个事务开始前,(1,1)的row_trx_id为90,由很早的事务修改
那么A的视图数组为 [ 99,100 ]
B为 [99,100,101]
C为[99,100,101,102]
4.事务C先提交(1,1)->(1,2),row_trx_id改为102,90为历史版本
5.事务B更新(1,1)->(1,3),row_trx_id为101,102为历史版本,自己查询时102为自己更新的版本,可见
6.事务A查询,事务B未提交,但是101为最新版本,101 比高水位100大不可见,根据undo log往上找,102也大于100不可见,继续往上,直到90版本(1,1)。
小结
1.版本未提交不可见
2.版本已经提交,但是在视图创建后提交,不可见
3.版本已提交,但是在视图创建前提交,可见
这也就是可重复读的隔离级别
事务B的(1,3)没提交不可见,事务C的(1,2)虽提交但是在视图创建之后提交不可见,(1,1)可见
关于事务B能查到(1,3)且是在(1,2)的基础上进行更新是因为当前读,即更新数据都是先读后写,这个读只读当前值,相当于
select k from t where id=1 for update
共享锁和排他锁也是当前读
若进行这个当前读时,事务C还未提交怎么办?

这上面介绍的一系列数据版本控制也就是MVCC机制。
事务的功能就是靠二阶段日志,锁和MVCC三个技术实现的!
网友评论