mysql 隔离级别 快照_MySQL事务隔离级别

(1)读未提交,Read Uncommitted(脏读):这个很坑爹,就是说某个事务修改的数据还没提交的时候,就让别的事务给读到了,这就恶心了,很容易导致出错的。

(2)读已提交,Read Committed(不可重复读):这个比上面那个稍微好一点,但是一样比较尴尬就是说事务A在准备付款50元钱的时候, 先查询了余额,是100,足够付款。然后过了段时间,事务B把那个100扣掉了,并且还提交了,此时事务A确认付款,但是账户金额却不够扣了。

这是读了人家事务提交的数据啊,所以是读已提交。

这也叫做不可重复读,就是所谓的一个事务内对一个数据两次读,可能会读到不一样的值。

(3)可重复读,Read Repeatable:这个比上面那个再好点儿,就是说事务A在执行过程中,对某个数据的值,无论读多少次都是值1;哪怕这个过程中事务B修改了数据的值还提交了,但是事务A读到的还是自己事务开始时这个数据的值。

原因:可重复读默认使用的读取方式是快照读。

(4)幻读:不可重复读和可重复读都是针对两个事务同时对某条数据在修改,但是幻读针对的是插入。

幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的数据。

那么幻读会带来啥问题呢?例如:事务1要插入一条数据,我先查询一下有没有相同的数据,但是这时事务2添加了这条数据,这就会导致事务1插入失败,并且它就算再一次查询,也无法查询到与其插入相冲突的数据,同时自身死活都插入不了。

来分析下情形:

1)T1时刻 读取年龄为20的数据, Session1拿到了2条记录。

2)T2时刻 另一个进程Session2插入了一条新的记录,年龄也为20

3)T3时刻,Session1再次读取年龄为20的数据,发现还是2条数据,貌似 Session2新插入的数据并未影响到Session1的事务读取。

对于T1 -- T3 时刻的情形,从结果来看,在可重复度读隔离级别下似乎解决了幻读的问题。

4)T4时刻,Session1 修改年龄为20的数据, 发现影响行数为3条。 为什么T3时候只能查到2条数据,但现在修改确修改了3条数据?

5)T5时刻,Session1 再次读取年龄为20的数据,发现结果变成了3条,我们知道被修改的第三条就是Session2在T2时刻新增的一条。

T4,T5 的结果来看,Session1 读到了 Session2 新插入的数据,产生了幻读现象。

(5)串行化:如果要解决幻读,就需要使用串行化级别的隔离级别,所有事务都串行起来,不允许多个事务并行操作。

MySQL的默认隔离级别是Read Repeatable,就是可重复读,就是说每个事务都会开启一个自己要操作的某个数据的快照,事务期间,读到的都是这个数据的快照罢了,对一个数据的多次读都是一样的。

快照读

简单的select操作(不包括 select ... lock in share mode, select ... for update)

那么快照读究竟是什么呢,是对当前数据的全量拷贝吗?每开启一个事务,都要把当前数据库的数据拷贝一份出来?

很明显不是。

一方面,这样做太消耗内存了,另一方面,这样会很慢。

快照读其实很简单,也很绝妙 —— 数据版本,也就是我们常说的MVCC,多版本并发控制。

1)Innodb里面,每行数据,都可以有多个版本,每个版本都有一个字段trx_id,记录生成这个版本的事务的ID。

假设一开始,id=1这行数据,只有一个版本,trx_id是90,意味着生成这个版本的事务ID是90。

这时候Session A开始了,从上一讲,我们已经知道,begin时并不会生成快照,快照在第一次select时才会生成,那么第一次select时,Session A都做了什么呢?

2)Session A只需要做一件事:用一个数组(ReadView),来记录当前活跃的事务ID。

假设session A的事务ID是97,当前还有另外两个事务,事务ID是94、96,所以session A会生成一个 [94,96,97] 的数组,也叫ReadView,而ReadView中存储我们系统中当前活跃着的读写事务的trx_id。

注意:是系统中所有的活跃事务的trx_id,例如,查询符合 id = 1 这个条件的数据的所有的活跃事务。

3)这个数组有什么用?后面你就知道了。

接着,session B执行了update语句,来更新id=1这一行数据,给这一行数据生成一个新的版本,假设session B的事务ID是98,因此这行数据就有了两个版本。

4)这时候,session A又来select了,当前版本是session B生成的,那session A是如何找到之前的版本的呢?

这时候,session A一开始生成的事务数组就派上用场了,session A的事务数组是[94,96,97],最小事务ID是94,最大事务ID是97,所以,当它遇到一行数据时,会先判断这行数据的版本号X:

如果X大于97,那么意味着这行数据,是在session A开始之后,才提交的,应该对session A不可见。

如果X小于97,那么分两种情况:

1.如果X在数组里面,比如X是96,那么意味着,当session A开始时,生成这个版本的数据的事务,还没提交,因此这行数据对Session A不可见。

2.如果X不在数组里面,比如X是95,那么意味着,当session A开始时,生成这个版本的数据的事务,已经提交,因此这行数据对Session A可见。

5)好,现在session A开始遍历 id=1 这行数据的所有版本:

当前版本是98,大于97,表示是在 session A 的事务之后生成的版本,所以不可见,继续看上一个版本,96也不符合,再往上,再往上,可能看到的版本是90,小于94,可见,就它了,而为什么要小于94呢?

因为94作为当前数据中的最早的版本,既然被记录进来了, 那么就证明94是跟97在同一时间活跃的事务,所以可能还未提交,那么自然不能读它的数据,而小于94的版本,意味着在Session A事务之前就已经提交了,所以就可以读。

小结:

大于当前事务的trx_id的数据版本不能读。

小于当前事务的trx_id,并且不在快照数组中的版本,可以读。

不确定,好像当前的事务id版本也不可以读?因为是未提交版本?

那么所有的版本的数据在哪呢?

答案是 -> 版本链,从版本链中先找最近记录,如果最近这一条记录事务id不符合条件,不可见的话,再去找上一个版本再比较当前事务的id和这个版本事务id看能不能访问,以此类推直到返回可见的版本或者结束。

举个例子 ,在已提交读隔离级别下:

比如此时有一个事务id为100的事务,修改了name,使得的name等于小明2,但是事务还没提交。则此时的版本链是,如下:

005c2a2048666493f49a1df10f9b4723.png

那此时另一个事务发起了select 语句要查询id为1的记录,那此时生成的ReadView 列表,也就是数组,只有[100]。那就去版本链去找了,首先肯定找最近的一条,发现trx_id是100,也就是name为小明2的那条记录,发现在列表内,所以不能访问。

这时候就通过指针继续找下一条,name为小明1的记录,发现trx_id是60,小于列表中的最小id,所以可以访问,直接访问结果为小明1。

那这时候我们把事务id为100的事务提交了,并且新建了一个事务id为110也修改id为1的记录,并且不提交事务

e7037cea0cb59f962d3627e7caf8e43c.png

这时候版本链就是

64766b406f50624032d866347cf2e585.png

这时候之前那个select事务又执行了一次查询,要查询id为1的记录。

这个时候关键的地方来了

如果你是读已提交隔离级别,这时候你会重新创建一个ReadView,那你的活动事务列表中的值就变了,变成了[110]。

按照上的说法,你去版本链通过trx_id对比查找到合适的结果就是小明2。

如果你是可重复读隔离级别,这时候你的ReadView还是第一次select时候生成的ReadView,也就是列表的值还是[100]。所以select的结果是小明1。所以第二次select结果和第一次一样,所以叫可重复读!

在《Mysql实战45讲》里,丁奇给出了这样一个“等价判断”可见性的原则:

版本未提交,不可见;

版本已提交,但是是在快照创建后提交的,不可见;

版本已提交,而且是在快照创建前提交的,可见;

这其实就是可重复读的想要实现的效果。

当前读

读取的是记录数据的最新版本,并且当前读返回的记录都会加上锁,保证其他事务不会再并发的修改这条记录

概念说的比较虚,也不好理解,接着举一个例子吧,假设你开启了两个事务,分别是A和B,这里有个张表,user表,里面有四条数据

d9386dd4f7e81b64618bcb15ed4e48d2.png

查询如果想要使用当读的话可以手动加上 lock in share mode 或者 for update ,例如:

select ... lock in share mode, select ... for update

update、insert、delete

当执行这几个操作的时候默认会执行当前读,也就是会读取最新的记录,也就是别的事务提交的数据你也可以看到,这样很好理解啊,假设你要update一个记录,另一个事务已经delete这条数据并且commit了,这样不是会产生冲突吗,所以你update的时候肯定要知道最新的信息啊。

我在这里介绍一下update的过程吧,首先会执行当前读,然后把返回的数据加上锁,之后执行update。加锁是防止别的事务在这个时候对这条记录做什么,默认加的是排他锁,也就是你读都不可以,这样就可以保证数据不会出错了。但注意一点,就算你这里加了写锁,别的事务也还是能访问的,是不是很奇怪?数据库采取了一致性非锁定读,别的事务会去读取一个快照数据。

innodb默认隔离级别是RR, 是通过MVVC来实现了,读方式有两种,执行select的时候是快照读,其余是当前读,所以,mvvc不能根本上解决幻读的情况。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值