事务隔离级别中存在可重复读,在事务启动时会生成一个视图read-view,之后事务中的任何需求数据都会来源于该视图,即使其他事务对某些数据进行了修改,那可重复读隔离又是如何实现的呢?
如果一个事务在可重复读隔离级别下,其他事务对某行数据进行了修改,而后当前事务又对那行事务进行修改并读取,那读取的数值还是和事务启动时看见的一样吗?
现在,我们就是来来探究这两个问题的,来看图:
mysql> 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);
这里有两个关注点:
-
start transaction with consitent snapshot 和start/begin transaction 的区别
- start transaction with consitent snapshot 会马上生成一个事务并且创建一致性视图
- start transaction不会马上创建事务,会在第一个sql语句的时候再创建事务并创建视图
-
“autocommit=1”设置下,事务C的update语句本身为一个事务,且自动提交且隔离级别REPEATABLE-READ,可用show variables like 'transaction_isolation’查看。
好,下面就是求事务A,事务B的值分别为多少?这里其实不难猜到事务A get到的值应该为1,但是事务B get到的值确实为3,跟着下面来看吧!
8.1 快照如何实现
在可重读隔离级别下的事务中都会有一致性视图,那视图又是如何定义的呢?
来看,在InnoDB里每个事务都有事务ID,叫作transaction_id,它们是在事务开始的时候向InnoDB的事务系统申请的,事务的编号都是按顺序严格递增的。
数据表中的每行数据都有会多个版本,每次事务更新数据时(即使事务没有提交),都会生成一个新的数据版本,事务会把transaction_id赋值给新的数据版本的事务ID,也叫row_trx_id。同时旧的数据版本还要有迹可循,必须有办法拿到他。
也就是说,数据表中每一行数据,都是有多个版本的,每个版本都记录着对应的row_trx_id,row_trx_id就是更新数据的事务ID-transaction_id。
再来,看图:
图中有4个数据版本V1-V4,3条sql语句和3条undo log(回滚日志)。undo log是V4→V3之间的虚线,同理,共三条。
我们来看拿下,上图中最新的数据版本为V4,k=22,row_trx_id=25,表明是transaction_id为25的事务更新数据生成的它。上图中的V1,V2,V3并不是真实存在的,它都是通过回滚日志来得到的,我们可以通过3条回滚日志得出V1,V2,V3中任意数据版本,这就是MVVC的实现机制。
我们继续来说可重复读隔离级别,事务一启动生成视图,不是对已提交的事务可见,没有提交的事务不可见嘛,那事务又如何去分辨事务已经提交了呢?
在InnoDB中,InnoDB引擎会为每个事务启动的时候构建一个数组,数组里保存的是当前活跃的事务transaction_id,活跃就是在运行,但未提交的。将数组中最小定位为低水位,最大值定义为高水位。
来,再看图:
此时,我们需要操作的行数据row_trx_id会有三种情况:数据版本在不可见情况下,会循着回滚日志去找可见的最新数据版本。
- row_trx_id小于低水位值,处于绿色已提交事务中,对于我们来说是可见
- row_trx_id大于低水位,小于高水位,处于黄色未提交事务中,这里就需要分辨row_trx_id是否在数组中,这数组指的是事务开始构建的transaction_id数组。
- 在数组中,事务未提交,不可见
- 不在数组中,事务已提交,可见,后来的事务先提交的原因是因为一些短事务。
- row_trx_id大于高水位,会向前寻找数据版本。
InnoDB就是利用MVVC特性和事务开始时创建的transaction_id数组来实现视图的。
8.2更新逻辑
这里主要明白两条规则:
- 更新数据都是先读后写,读取的数据为当前的最新值版本,称为当前读。
- 如果更新最新数据版本的事务未提交,这就涉及到行锁了,事务会挂起,直到写锁释放。
好,现在可以来解决开始的问题了,上图:
假设有事务D将(id,k)设置为(1,1)。我们看下各个事务能拿到的数据版本编号。
事务A:[A,D],事务B:[A,B,D],事务C:[A,B,C,D]
事务A为可重复读隔离级别对事务B和事务C所做的修改一无所知,所以get到的k=1.
事务B中存在update语句,update语句根据上面讲的规则,读取的为最新数据版本,也就是2,加一就是3,所以get到的key = 3。
问题练习:
- start tranaction with consitent snapshot 和start tranaction 指令的区别?
- autocommit = 1会出现什么状况?
- innodb支持RC和RR隔离级别实现是用的一致性视图
- 事务在启动的时候会生成一个“快照”,快照是怎么回事?MVVC特性如何实现?
- 当前读又是怎么一回事?