MySQL中的当前读


微信搜索“coder-home”或扫一扫下面的二维码,关注公众号,第一时间了解更多干货分享,还有各类视频教程资源。扫描它,带走我


前言

我们知道脏读、不可重复读、幻读的概念和区别,也知道事务的四种隔离级别:读未提交、读已提交、可重复读、串行化的含义。如果你对这些概念仍然有疑问,关注我,在主页中找到之前分享的文章:MySQL中的事务的理解。

事务的隔离级别的提出就是为了修复事务在并发的情况下读数据所发生的各种问题。

  • 为了修复脏读的问题,我们提出了读已提交的事务隔离级别;
  • 为了修复不可重复的问题,我们提出了可重复读事务隔离级别;
  • 为了修复幻读的问题,我们提出了串行化的事务隔离级别。

注意:在MySQL中,修复幻读的时候,并没有使用到串行化的事务隔离级别,而是使用了MVCC多版本并发控制和Next key lock临键锁的方式来修复幻读问题的。

那么什么是当前读呢?这个是什么意思?接下来我们一起来研究一下。

当前读

为了说明什么是当前读,我们来做一个实验,该实验的环境是MySQL5.7版本,事务隔离级别为RR可重复读。使用的存储引擎是innodb存储引擎。基于实验,能够更深刻的理解当前读的含义。

准备实验环境

准备使用使用的表和初始化数据如下:

create table t(id int, v int);
insert into t values(1,1);
insert into t values(2,2);
select * from t;

开始实验

准备好环境之后,我们同时开启三个事务会话,分别如下图所示。然后按照左侧的时间点依次执行验证最后的结果是否和下面截图中的结果一致。

实验步骤如下:

  1. T1时刻,开启事务A。
  2. T2时刻,开启事务B。
  3. T3时刻,分别在事务A、事务B查询表t中id为1的行对应的列v的值。与此同时,在事务C中也查询一下v的值。此时的v的值在三个事务中的值都为1。
  4. T4时刻,在事务C中,修改v的值自加1,事务C不是使用显示的声明来开启的。所以修改完成后,也就自动提交了事务C。
  5. T5时刻,修改并提交了事务C后,在事务A,事务B和事务C中,在依次查询一下v的值。在事务A和事务B中都为1,因为在这两个事务启动的时候,他们已经创建好了一致性视图,此时他们是一致性的快照读,在可重复读事务隔离级别下,此时事务A和B中,v的值为仍然为1。而事务C的值之所以为2是以为此时的事务C已经修改且已经提交。所以它能看到最后提交的v的值,是2。
  6. T6时刻,在事务B中更新了v的值,让v的值在原来的v值基础上自加1。
  7. T7时刻,分别在事务A和B中查询v的值,在事务C结束之后也查询了v的值。此时发现在事务A中,v的值为1,遵循的是在事务A运行期间可重复读的原则,所以,在一致性快照读的时候,v的值仍然是1。但是在事务B中查询发现v的值是3而不是2,是因为在更新的时候,要读后写,此时的读是读取了数据库中最新的v的值为2,发生了当前读,然后再2的基础上再次加1得到了3。所以事务B中v的值为3。在事务C之后的查询就是一个普通的查询,它没有任何事务包裹,所以它要查询到数据库中最新的v值2,3之所以看不到是因为事务B还没有提交。
  8. T8时刻,事务A执行提交操作。
  9. T9时刻,在事务A结束之后查询v的值,此时v的值为2,因为事务A已经结束,此时的查询是一个普通的查询或获取到数据库中最新的v的值。事务C之后的查询和事务A之后的查询一样,结果也是v的值为2。而事务B此时还没有提交,所以在事务B中的此时的查询结果应该获取当前事务中最v的修改后的结果,v的值是3。
  10. T10时刻,事务B执行提交操作。
  11. T11时刻,分别在事务A、事务B、事务C后面都查询一次v的值,此时最后的事务B已经提交完成。所以这三个查询的值都为3。

实验路程截图如下:
在这里插入图片描述

这里需要注意我们实验的时候,开启事务的方式使用的是:start transaction with consistent snapshot; 而不是我们经常使用的begin;这两种开启事务的方式有一点区别。区别如下:

  1. start transaction with consistent snapshot:会马上开启事务,获取事务的ID,同时创建一个一致性事务视图。
  2. begin:并不是马上开启事务,事务ID也不是马上就获取。而是在begin后面的第一个操作innodb存储引擎表的SQL语句的时候才创建的一致性视图并获取事务ID的。

另外,我们在前面提到了快照读。所谓的快照读是指,在一个事务执行的过程,在这个事务当中执行一些普通的查询,这些查询没有什么特殊的用法,只是为了获取某些数据行的结果,这样的查询就是快照读。快照读在获取数据行的时候,是从一致性视图中获取数据的,目的是为了满足在事务运行期间对数据的可重复性读的要求,在一致性视图中的数据,是在事务启动的时候就已经创建好的,它里面的值将贯穿整个事务运行期间所有的快照读。

当前读的概念

从前面的实验中,我们发现在事务B修改数据的时候,它修改的时候是基于事务C修改提交后的结果之上才做的修改,修改数据的时候,需要先查询出来数据,然后才能基于查询出来的数据的基础之上做出修改。这里的先查询数据后修改的查询的操作就是当前读,当前读的时候,是需要读取数据库中最新的数据内容。所谓最新的数据内容是修改且已经提交的数据。

试想一下,如果事务B,不去执行当前读,而直接基于一致性视图去读取数据,然后再去修改数据,就会把事务C已经修改且提交的数据给覆盖掉,而这是不允许的。

什么时候会发生当前读

除了在执行修改语句的时候,需要执行当前读,然后再更新数据之外,select语句也有可能是当前读。比如:下面的两个SQL语句也会发生当前读。

select v from t where id=1 for update; -- 给ID为1的行增加写锁(X锁,排它锁)
select v from t where id=1 lock in share mode; -- 给ID为1的行增加读锁(S锁,共享锁)

更深刻认识当前读

在我们更新表中的数据的时候,有的时候一次性的更新多个列的值,这多个待更新的列之间又有业务逻辑上的关系。这个时候我们在更新的时候一定要格外的注意,更新过程中的当前读,这里的当前读是指读取当前事务中的值。

我们来看一个例子:

CREATE TABLE `test` (
  `id` int(255) DEFAULT NULL,
  `a` int(255) DEFAULT NULL,
  `b` int(255) DEFAULT NULL,
  `c` int(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `test`(`id`, `a`, `b`, `c`) VALUES (1, 100, 100, 100); 

更新语句如下:

update test 
set a=a-1, b=a-1-1, c=a-1-1-1 
where id=1;

此时,你先自己口算一下,最后这个表中的这一行数据的abc三列的值分别是多少呢?答案是:a=99, b=97, c=96。你可能会好奇,为什么不是:a=99, b=98, c=97呢?因为abc原先都为100,那么a=a-1=100-1=99;b=a-1-1=100-1-1=98才对呀?为什么b的值最后是97呢?

这里我们分析一下。

在执行a=a-1这个语句块,MySQL在计算a到底需要等于多少的时候,要获取数据库中a的值,此时得到的a为100,那100-1后赋值给了a,在当前这个更新语句的事务中,a此时为99了。

在执行b=a-1-1的时候,它会获取a的值然后再去为b赋值,因为这个更新语句是一个事务,所以这个事务前面修改的a的值,当前的事务还是会承认的,所以它读到a的值为99,此时再执行b=99-1-1=97,

在执行c=a-1-1-1的时候,同样会读取当前事务中a的值为99,然后计算出c=99-1-1-1=96。

如果我们把上面的更新语句改为下面的SQL语句,结果又会如何呢?

update test 
set  b=a-1-1, c=a-1-1-1, a=a-1
where id=1;

赶紧自己动手试一下吧。


微信搜索“coder-home”或扫一扫下面的二维码,关注公众号,第一时间了解更多干货分享,还有各类视频教程资源。扫描它,带走我


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值