事务的隔离性

问题背景

在一个事务的执行期间,如果这个事务的隔离级别是可重复读的,那么这个事务读取到的数据就是和启动的时候读取到的数据是一样的。那么,在这个事务之前,有一个 B 事务在执行,执行后,更新了一行数据,那么这个事务在 B 事务的执行之后,读取到的数据是怎样的呢?是和开始启动的时候一样,还是在 B 事务的执行后的数据的呢?

为了回答这个问题,我们来对三个事务进行分析:
image

这是三个事务,默认 id 为 1 的时候 k 的值为 1。

注意点:在 begin/start transaction 的时候并不是一个事务的起点。只有在执行到数据的具体的语句的时候事务才开始启动的。 start transaction with consistent snapshot 可以快速启动一个事务。

  • 这个时候,提出一个问题,事务 B 查到的值为多少?事务 A 查到的值为多少?
    答案:事务 B 查到的值为 3,事务 A 查到的值为 1.

  • Mysql中有两个视图的概念,一个是 view,这个视图的是一个用查询语句定义的虚拟表,在调用的时候,直接是执行查询语句,然后生成结果。创建的视图的语句是 create view 。

  • 另一个视图的是 InnoDB 在 MVCC 中的创建的一致性读视图,即是 consitent read view 用来支持的是 RC ( read Commited) 和 RR (Repeatable Read,可重复读)隔离级别的实现。

  • 这两个视图都是没有物理结构的,作用仅仅是事务执行期间用来定义”事务能读到什么样的数据”。

快照在MVCC中的原理

可以这么理解,在快照中读取的数据是在视图创建前的已经提交的数据。所以这就等于是静态的数据,所以在快照中创建的时候就会比较快。

目前有三个事务:A、B、C,如图所示,A的版本号是100,B的版本号是101,C的版本号是102,
结果就是在 A 的查询数据的时候,获到的 K 的值是 1,在事务B的时候,set 成功后,得到的值是 3,在事务 c 的时候,set 成功的值是 2
image

  • 这里有个细节,在事务C还没有提交的时候,为什么,事务 B 可以读到改变的值,原因就是,在 update 的时候,先是去读数据的当前版本号,然后再更新,然后这个时候事务 B 的查询语句获取到的当前值的版本号是 101,也是它自身的版本号,所以,就可以直接拿来用。

    这里有一个原则:更新数据都是先读后写的,这个读,读的是当前的值,所以这叫当前读。除了 update 语句,select 语句如果加锁,也可以是当前读的。

1
2
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

这是在第一行的数据加了共享锁(读锁),在第二行加了写锁,也就是排他锁。

事务C的逻辑发生改变

image
这个时候,在事务 B 的时候,只能等待事务 C 提交后才能进行 set 值。这就是另外一种情况,两阶段的锁协议。

小结

InnoDB 的数据有多个版本,每个数据的版本都有自己的row trx_id,每个事务都有自己的一致性视图。普通的查询语句是一致性读,一致性读会根据 row try_id 和一致性视图确定数据版本的 可见性。

  • 可重复读和读提交的的区别是:可重复读中,查询只承认是指在事务创建之前的数据,读提交的话就是在只承认语句在启动之前就已经完成提交的数据。

当前读:就是只读当前的最新版本。

参考文章:

《MySQL45讲》专栏中的【事务到底是隔离的还是不隔离的?】

spacedong wechat
愿意交个朋友吗~
觉得有收获么