不可重复读和幻像读之间有什么区别?


154

不可重复读和幻像读之间有什么区别?

我已经阅读了WikipediaIsolation(数据库系统)文章,但是我有一些疑问。在下面的示例中,将发生什么:不可重复读取幻像读取

交易A
SELECT ID, USERNAME, accountno, amount FROM USERS WHERE ID=1
输出:
1----MIKE------29019892---------5000
交易B
UPDATE USERS SET amount=amount+5000 where ID=1 AND accountno=29019892;
COMMIT;
交易A
SELECT ID, USERNAME, accountno, amount FROM USERS WHERE ID=1

在上面的示例中,另一个疑问是应使用哪个隔离级别?又为什么呢


Answers:


164

来自维基百科(对此有很多详尽的示例):

发生不可重复的读取,这是因为在事务过程中,一行被检索两次,并且两次读取之间的值不同。

当在事务过程中执行两个相同的查询,并且第二个查询返回的行集合与第一个查询不同时,将发生幻像读取。

简单的例子:

  • 用户A两次运行相同的查询。
  • 在这两者之间,用户B运行事务并提交。
  • 不可重复读取:用户A查询的A行第二次具有不同的值。
  • 幻像读取:查询中的所有行在前后都有相同的值,但是选择了不同的行(因为B已删除或插入了一些行)。示例:select sum(x) from table;即使没有添加任何受影响的行,即使添加或删除了行,也将返回不同的结果。

在上面的示例中,将使用哪个隔离级别?

您需要哪种隔离级别取决于您的应用程序。“更好的”隔离级别(例如并发性降低)的代价很高。

在您的示例中,您不会读取幻像,因为您仅从单行(由主键标识)中进行选择。您可以进行不可重复的读取,因此,如果这是一个问题,则可能需要一个隔离级别来防止这种情况。在Oracle中,事务A也可以发出SELECT FOR UPDATE,然后事务B直到A完成后才能更改行。


6
我真的不明白这样的语法逻辑...一个不可重复的,当在读时读重复(并获得不同的值)?? ...!
serhio

14
@serhio“不可重复”是指您可以读取一次值并获得x作为结果,然后再次读取并获得y作为事实,因此您无法重复(不可重复)两个结果相同同一行的不同查询,因为该行值在两次读取之间进行了更新。
BateTech's

@Thilo任何实际的用例示例,其中可重复读取可能会产生问题并且有必要吗?
user104309

如果在另一笔交易中修改了PK,该怎么办?会导致幻像读取吗?(在大多数情况下,这是一件奇怪的事情,但并非不可能。)
jpmc26 '18

1
两种声音对我来说都是一样的
-sn.anurag

125

我喜欢考虑的一种简单方法是:

不可重复读取和幻像读取都与来自另一个事务的数据修改操作有关,这些操作是在事务开始之后提交的,然后由事务读取。

不可重复读取是指您的事务从另一个事务中读取已提交的UPDATES。现在,同一行的值与交易开始时的值不同。

幻像读取类似,但是当从已提交的INSERTS和/或从另一个事务中删除时读取。自您开始事务以来,有新的行或行消失了。

脏读取类似于不可重复和幻像读取,但与读取未提交的数据有关,并且在读取来自另一个事务的UPDATE,INSERT或DELETE并且另一个事务尚未提交数据时发生。它正在读取“进行中”的数据,该数据可能不完整,也可能从未真正提交。


4
它与事务隔离级别和并发性有关。使用默认的隔离级别,您将不会得到脏读,并且在大多数情况下,您希望避免脏读。有隔离级别或查询提示将允许脏读,在某些情况下,这是可以接受的折衷方案,以实现更高的并发性,或者由于边缘情况(例如,对来自另一个连接的正在进行中的事务进行故障排除)而必须这样做。最好的做法是,脏读的想法不会对您通过“气味测试”,因为通常来说,应该避免使用它们,但确实有目的。
BateTech

1
@PHPAvenger在这里是READ UNCOMMITTED隔离级别的用例:在select和update查询之间总是有可能遇到死锁(在此处解释)。如果选择查询太复杂而无法创建覆盖索引,则为了避免死锁,您将希望使用READ UNCOMMITED隔离级别,但有遇到脏读的风险,但是您回滚事务的频率却不必担心这些脏读是永久的吗?
petrica.martinescu

1
@ petrica.martinescu由脏读引起的问题不仅与事务是否回滚有关。脏读可能会返回非常不准确的结果,具体取决于如何修改未决事务中的数据。想象一个执行一系列删除,更新和/或插入的事务。如果使用“读取未提交”在该事务的中间读取数据,则该数据不完整。快照隔离级别(在SQL Server中)是读取未提交的更好的选择。IMO中很少有有效的用例,用于读取未提交的隔离级别。
BateTech'3

2
@DiponRoy很好的问题。如果使用可重复读(RR)隔离,则实现的锁定应防止在选定的行上发生删除。多年来,我对2个iso级别的定义有所不同,主要是幻像是在collection /#返回的行中发生的更改,而RR是在更改的同一行。我刚刚检查了更新的MS SQL文档,说删除会导致非RR(docs.microsoft.com/en-us/sql/odbc/reference/develop-app/…),所以我认为将删除分组RR类也是如此
BateTech '18

2
@anir yes插入和删除包含在脏读取中。示例:开始交易,在连接a上插入100条发票行中的2条,现在连接b在提交trx之前和添加其他98条行之前读取这两行,因此不包括发票的所有信息。这将是涉及插入的脏读。
BateTech '19

28

本文所述,“ 不可重复读取”异常如下所示:

在此处输入图片说明

  1. 爱丽丝和鲍勃开始两个数据库事务。
  2. 鲍勃(Bob's)读取帖子记录,标题列的值是Transactions。
  3. 爱丽丝将给定帖子记录的标题修改为ACID的值。
  4. 爱丽丝提交她的数据库事务。
  5. 如果鲍勃(Bob's)重新阅读该帖子记录,他将观察到该表行的其他版本。

在有关Phantom Read的本文中,您可以看到这种异常可能发生如下:

在此处输入图片说明

  1. 爱丽丝和鲍勃开始两个数据库事务。
  2. Bob读取与标识符值为1的帖子行关联的所有post_comment记录。
  3. Alice添加了一个新的post_comment记录,该记录与标识符值为1的帖子行相关联。
  4. 爱丽丝提交她的数据库事务。
  5. 如果Bob重新读取post_id列值等于1的post_comment记录,他将观察到该结果集的另一个版本。

因此,虽然不可重复读取适用于单行,但幻像读取涉及满足给定查询过滤条件的一系列记录。


3
极好的可视化@Vlad
dextermini '18

23

阅读现象

  • 脏读:从另一个事务中读取未提交的数据
  • 不可重复读取UPDATE从另一个事务的查询中读取COMMITTED数据
  • 幻像读取:从INSERTDELETE其他事务查询中读取提交的数据

注意:在某些情况下,来自另一个事务的DELETE语句引起非重复读取的可能性也很小。不幸的是,当DELETE语句删除当前事务正在查询的同一行时,就会发生这种情况。但这是一种罕见的情况,在每个表中都有数百万行的数据库中更不可能发生。在任何生产环境中,包含事务处理数据的表通常具有很高的数据量。

我们还可以观察到,在大多数用例中,UPDATE可能是更频繁的工作,而不是实际的INSERT或DELETES(在这种情况下,仅存在不可重复读取的危险- 在那些情况下不可能进行幻像读取)。这就是为什么对UPDATES与INSERT-DELETE的区别对待,并且所产生的异常也被不同地命名的原因。

处理INSERT-DELETE而不是仅处理UPDATES还带来额外的处理成本。


不同隔离级别的好处

  • READ_UNCOMMITTED不会阻止任何操作。零隔离级别
  • READ_COMMITTED仅防止一次读取,即脏读
  • REPEATABLE_READ防止两个异常:脏读和不可重复读
  • SERIALIZABLE防止所有三种异常:脏读,不可重复读和幻像读

那么,为什么不始终设置事务SERIALIZABLE呢?好了,以上问题的答案是:SERIALIZABLE设置使交易非常缓慢,而我们再次不希望这样做。

实际上,交易时间消耗的速率如下:

可SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED > READ_UNCOMMITTED

因此READ_UNCOMMITTED设置是最快的


摘要

实际上,我们需要分析用例并确定隔离级别,以便我们优化事务处理时间并防止出现大多数异常情况。

请注意,默认情况下,数据库具有REPEATABLE_READ设置。


1
UPDATE或DELETE都可以用于不可重复的读取,或者仅仅是UPDATE?
Dipon Roy

1
UPDATE或DELETE都可以进行不可重复的读取
niket patel

实际上,我们可以总结出,平均而言,由同一数据库中的另一个事务执行的随机DELETE语句对当前事务造成不可重复读取的可能性非常低。但是,相同的delete语句有100%的机会引起对当前事务的Phantom读取。以这种方式看,如果您逐字逐句地看,我的写作是有点错误的。但是,我故意以此方式编写它,以使读者更清楚地了解内容。
Subhadeep Ray

+1是一种简单易懂的解释。但是我认为大多数数据库(oracle,mysql)的默认隔离级别为Read Committed,并且postgress可能使用default的repeatable_read
akila

7

这两种隔离级别之间的实现方式有所不同。
对于“不可重复读取”,需要行锁定。
对于“幻像读取”,需要范围锁定,甚至是表锁定。
我们可以通过使用两阶段锁定协议来实现这两个级别。


要实现可重复的读取或可序列化,无需使用行锁定。
a_horse_with_no_name

5

在具有不可重复读取的系统中,事务A的第二个查询的结果将反映事务B中的更新-它会看到新的金额。

在允许幻像读取的系统中,如果事务B要插入 ID = 1的新行,则当执行第二个查询时,事务A将看到新行;即幻像读取是不可重复读取的特例。


我认为幻读的解释不正确。即使从未提交的数据从不可见,也可以获得幻像读取。请参阅Wikipedia上的示例(在上面的评论中链接)。
Thilo 2012年

1

公认的答案表明,最重要的是,两者之间的所谓区别实际上根本不重要。

如果“某行被两次检索,并且该行中的值在两次读取之间不同”,则它们不是同一行(在正确的RDB中不是同一元组),因此根据定义,“第二个查询返回的行与第一个查询不同”。

对于“应该使用哪种隔离级别”这个问题,您的数据对某人越重要,在某个地方,序列化是您唯一合理的选择。


0

我认为非重复阅读和幻像阅读之间有一些区别。

不可重复意味着有两个交易A和B。如果B可以注意到A的修改,那么可能发生脏读,因此我们让B在A提交后注意到A的修改。

有一个新问题:我们在提交后让B注意到B的修改,这意味着A修改了B所持有的行的值,有时B会再次读取该行,因此B会获得与我们第一次不同的新值得到,我们称之为不可重复,以解决该问题,我们让B记住B开始时的某些事情(因为我不知道会记住什么)。

让我们考虑一下新的解决方案,我们还会注意到还有一个新问题,因为我们让B记住了一些东西,所以无论A发生了什么,B都不会受到影响,但是如果B想要在表和B中插入一些数据检查表以确保没有记录,但是此数据已由A插入,因此可能会发生一些错误。我们称之为幻影阅读。


0

不可重复读取是隔离级别,幻像读取(通过其他事务读取已提交的值)是一个概念(读取类型,例如脏读取或快照读取)。不可重复读取隔离级别允许幻像读取,但不允许脏读取或快照读取。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.