208

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

我已经阅读了来自 Wikipedia 的 Isolation (database systems) 文章,但我有一些疑问。在下面的例子中,会发生什么:不可重复读取幻读

交易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

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

4

10 回答 10

214

来自维基百科(其中有很好的详细示例):

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

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

简单的例子:

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

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

您需要什么隔离级别取决于您的应用程序。“更好”的隔离级别(例如降低并发性)的成本很高。

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

于 2012-06-15T05:17:37.187 回答
173

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

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

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

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

脏读类似于不可重复读和幻读,但与读取 UNCOMMITTED 数据有关,发生在读取另一个事务的 UPDATE、INSERT 或 DELETE,而另一个事务尚未提交数据时。它正在读取“进行中”的数据,这些数据可能不完整,并且可能永远不会真正提交。

于 2014-04-17T16:41:45.070 回答
66

不可重复读取异常如下所示:

不可重复读取

  1. Alice 和 Bob 启动两个数据库事务。
  2. Bob 读取了 post 记录,并且 title 列的值为 Transactions。
  3. Alice 将给定帖子记录的标题修改为 ACID 的值。
  4. Alice 提交她的数据库事务。
  5. 如果 Bob 重新读取 post 记录,他将观察到该表行的不同版本。

幻读异常可能发生如下:

幻读

  1. Alice 和 Bob 启动两个数据库事务。
  2. Bob's 读取与标识符值为 1 的帖子行关联的所有 post_comment 记录。
  3. Alice 添加一个新的 post_comment 记录,该记录与标识符值为 1 的帖子行相关联。
  4. Alice 提交她的数据库事务。
  5. 如果 Bob 重新读取 post_id 列值等于 1 的 post_comment 记录,他将观察到此结果集的不同版本。

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

于 2018-07-01T13:08:33.387 回答
48

阅读现象

  • 脏读:从另一个事务中读取 UNCOMMITED 数据
  • 不可重复读取UPDATE:从另一个事务的查询中读取 COMMITTED 数据
  • 幻读INSERT:从另一个事务中读取或DELETE查询中的 COMMITTED 数据

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

此外,我们可能会观察到,在大多数用例中,UPDATES 可能是比实际 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 设置。管理员和架构师可能倾向于选择此设置作为默认设置,以展示更好的平台性能。

于 2017-09-11T10:56:06.583 回答
9

这两种隔离级别在实现上是有区别的。
对于“不可重复读取”,需要行锁定。
对于“幻读”,需要范围锁定,甚至是表锁定。
我们可以通过使用两相锁定协议来实现这两个级别。

于 2015-05-18T06:46:58.937 回答
5

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

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

于 2012-06-15T04:59:47.877 回答
2

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

如果“一行被检索两次并且行内的值在读取之间不同”,那么它们不是同一行(在正确的 RDB 中不是同一个元组),那么根据定义,实际上也是“集合第二个查询返回的行与第一个不同”。

至于“应该使用哪种隔离级别”的问题,您的数据对某人、某处越重要,Serializable 就越是您唯一合理的选择。

于 2017-08-02T19:45:30.713 回答
0

我认为不可重复读取和幻读之间存在一些差异。

Non-repeateable 表示有两个事务 A 和 B。如果 B 可以注意到 A 的修改,那么可能会发生脏读,所以我们让 B 在 A 提交后注意到 A 的修改。

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

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

于 2018-01-22T13:48:15.123 回答
0

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

于 2019-05-23T07:14:45.983 回答
0

不可重复读取和幻读都来自一个事务 T1,该事务 T1 看到另一个事务 T2 在 T1 完成之前提交的更改。不同之处在于不可重复读取会为同一逻辑行返回不同的值。(例如,如果主键是employee_id,那么某个员工在两个结果中可能有不同的薪水。)幻像读取返回两组不同的行,但是对于出现在两组中的每一行,列值是相同的。

于 2021-10-28T17:30:17.513 回答