温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

什么是脏读与幻读

发布时间:2021-10-09 17:16:14 来源:亿速云 阅读:145 作者:iii 栏目:数据库

这篇文章主要讲解了“什么是脏读与幻读”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“什么是脏读与幻读”吧!

什么是脏读与幻读

select @@tx_isolation;

什么是脏读与幻读

MySQL就包含4种隔离级别,隔离的当然是数据。要修改隔离级别的话,可以使用下面的SQL语句。

set session transaction isolation level read uncommitted; set session transaction isolation level read committed; set session transaction isolation level repeatable read; set session transaction isolation level serializable;

ok,我们创建一张小小的测试表,来看一下并发环境下的魔幻效果。

CREATE TABLE `xjjdog_tx` (  `id` INT(11) NOT NULL,  `name` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',  `money` BIGINT(20) NOT NULL DEFAULT '0',  PRIMARY KEY (`id`) USING BTREE ) COLLATE='utf8_general_ci' ENGINE=InnoDB ; INSERT INTO `xjjdog_tx` (`id`, `name`, `money`) VALUES (2, 'xjjdog1', 100); INSERT INTO `xjjdog_tx` (`id`, `name`, `money`) VALUES (1, 'xjjdog0', 100);

1. 脏读

脏读,意思就是读出了脏数据。啥叫脏数据?就是另外一个事务还没有提交的数据。在read  uncommitted隔离级别下,就会出现脏读。比如下面这个时序

事务 A:set session transaction isolation level read uncommitted; 事务 B:set session transaction isolation level read uncommitted; 事务 A:START TRANSACTION ; 事务 B:START TRANSACTION ; 事务 A:UPDATE xjjdog_tx SET money=money+100 WHERE NAME='xjjdog0'; 事务 B:UPDATE xjjdog_tx SET money=money+100 WHERE NAME='xjjdog0'; 事务 A:ROLLBACK ; 事务 B:COMMIT ; 事务 B:SELECT * FROM xjjdog_tx ;

在这个场景下,money的原始值为100,分别在两个session中进行了加100的操作,然后回滚了其中的一个session事务。结果,经过查询,发现money的值保持100不变。也就是其中一次加100的操作被覆盖掉了。

什么是脏读与幻读

所以脏读发生有几个条件。

  • 高并发场景,在一个事务A开始之后还没结束之前,有另外一个事务参与了事务A所涉及的数据行读写

  • 事务隔离级别处于最低的读未提交

  • 在你使用到这些数据之后,事务A回滚,造成你之前拿到的数据已经不再存在

解决方式,只需要设置成隔离级别比read uncommitted高即可。

2. 不可重复读

把隔离级别设置成read  committed即可避免脏读,这其实非常好理解。脏读产生的根本原因就是在事务的执行期间有别的操作乱入,这个隔离级别要求事务A提交之后,修改后的值,才能被事务B读到,所以脏读是不可能会发生的,从根本上杜绝了。

但read commited会发生不可重复读的情况。

顾名思义,就是在一个事务周期内,对于一个值的读取,产生了两个结果。

不可重复读,证明了世界并不是总围绕着你转的。在你的事务执行期间,会有无数的其他事务执行,如果你的事务持续时间超过了这些事务,那么你就可能读到两个或者更多的值。

让我来给你讲一个故事。

从前,有一颗桃树,长了12棵桃子。有一只猴子,叫做xjjdog,它想吃上面的桃子,但桃子还不熟。

第二天去看的时候,它发现桃子少了一个,变成了11个,经过仔细打听,原来是被猴子A抢先吃掉一个。

第二天去看的时候,桃子又少了一个,变成了10个,原来是被馋嘴的猴子B吃掉一个。

如此这般,桃子一天天少了下去,只剩下最后的2个了,但桃子还是没熟。

再不摘桃子就没了,xjjdog摘下了最后的2个桃子,正打算大快朵颐,结果跳出一只猴子X,说我盯着这些桃子已经1年了...

在这故事中,猴子A、B的事务持续周期是1天;xjjdog的事务持续周期是直到桃子成熟;猴子X的持续周期更长,可能是一年。它们每天看到的桃子,并不总是12个。今天的桃子,可能被其他的猴子(事务)给吃掉了,造成了观测的结果是不一样的,这就是不可重复读的概念。

有时候,即使读到的值是一样的,也不能证明没问题。比如有财务挪用了2亿去炒股,然后在月底把2亿还了回来,虽然最终的金额都是一致的,但由于你的对账周期长,就发现不了这种差异。

如何解决不可重复读呢?先要看一下不可重复读是不是问题。

有的系统,要求的就是这样的逻辑,每次在事务中读取到不一样的值,它是可以忍受的。但如果你想要在桃子成熟之前,桃子的数量都在你的掌控之中,那不可重复读就是一种问题。

一种非常好的方式,就是xjjdog一直站在桃树地下。当有别的猴子想要摘桃,就把它赶走。这种方式可行,但在数据库中非常低效,这是serializable级别的做法。

MySQL有一个默认的事务隔离级别,叫做repeatable read,使用了MVCC的方式(innodb),要更轻量级一些。

3. 可重复读

这就是MVCC(Multi-Version Concurrency Control)的功劳了,它有三个特点。

每行数据都存在一个版本,每次数据更新时都更新该版本

修改时,拷贝一份,当前版本随意修改,事务之间无干扰

保存时比较版本号,如果成功commit覆盖原记录,失败则rollback

MVCC在InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。它的实现关键也有三项技术:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 3个隐式字段:DB_TRX_ID,最近修改它的事务ID;DB_ROLL_PTR,回滚指针,指向上一个版本;DB_ROW_ID,隐藏主键

  3. undo日志:的对同一记录的修改,会生成针对此记录的版本变更链表

  4. read view:快照读操作的时候,产生的读视图。除了使用上面的额外信息,它也会维护一个活跃的事务ID集合

一切的关键,就在于快照这两个字上面。

比如事务A对某个记录进行了快照读,那么在快照读的这一刻,就生成了一个Read  View。在这一刻,事务B和C,还没有commit,事务D和E,在建立ReadView那一刻之前,commit完成,那么这个Read  View,就不能够读到B和C的修改。

但可惜的是,可重复读,只能解决快照读的不可重复读,快照读的时机,也会影响读取的准确程度。请看下面两种情况。

下面这种情况读到的是500。

事务A事务B
开启事务开启事务
快照读(无影响)查询金额为500快照读查询金额为500
更新金额为400 
提交事务 
 select 快照读金额为500
 select lock in share mode当前读金额为400

下面这种情况读到的是400。

事务A事务B
开启事务开启事务
快照读(无影响)查询金额为500 
更新金额为400 
提交事务 
 select 快照读金额为400
 select lock in share mode当前读金额为400
 

(表格来自[SnailMann]的博客)。

4. 幻读

幻读,这个词本身就非常的迷幻。在RU、RC、RR级别下,都会出现幻读。

拿一个最简单的例子来说。让你select一条记录是否存在然后打算进行后续插入时,如果这条记录不存在,然后你执行了插入操作,但在实际执行插入操作的时候,结果却报错了,这条记录已经存在了,这就是幻读。

首先,确认目前时可重复读级别。如果不是,则修改之。

SELECT @@tx_isolation # set session transaction isolation level repeatable read

让我们来看一下这个灵异过程。

什么是脏读与幻读

有5个步骤,我都给你标好了。下面一一介绍。

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 事务A使用begin开启一个事务,然后查询id为3的记录,此时不存在。但由于快照读开启了一个针对于id为3的记录的read  view,所以在这个事务自始至终都不能够读到为3的记录。很好,这就是我们不可重复读所需要的

  3. 接下来,事务B插入了一条id为3的记录,并提交成功

  4. 事务A此时也想插入这条记录,于是执行了相同的插入操作,结果数据库报错,显示这条记录已经存在

  5. 事务A此时一脸懵逼,想看一下这条记录到底是啥,但当它再次执行select语句的时候,却查不到这条记录

  6. 但在其他事务中,是可以看到这条记录的,因为它已经正确提交

这就是幻读。

5. 如何解决幻读

幻读有错么?多数情况下没错,就是报错怪异了些。要防止幻读,需要开启FOR UPDATE这样高强度的锁定,实际情况是非常少用。

为什么上面的操作,insert能报错,但select却无法查到数据呢?这就不得不提一下数据库读的两种模式:

快照读:普通的select操作,是从read view中读取数据,读取的可能是历史数据

当前读:insert、update、delete、select..for update这种操作,读取的总是当前的最新数据

对于当前读,你读取的行,以及行的间隙都会被加锁,直到事务提交时才会释放,其他的事务无法进行修改,所以也不会出现不可重复读、幻读的情形。所以insert能够发现冲突,而普通select却不可以。要想解决幻读,就需要加X锁。在上面这种情况,就可以在事务A中执行:

SELECT * FROM xjjdog_tx WHERE id=3 FOR UPDATE

当这么做的时候,即使id为3的记录不存在,它也会创建锁(在背后可能根据记录的存在与否加行X锁或者next-key lock间隙x锁)。

感谢各位的阅读,以上就是“什么是脏读与幻读”的内容了,经过本文的学习后,相信大家对什么是脏读与幻读这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI