不可重复读和幻读有啥区别?

Posted

技术标签:

【中文标题】不可重复读和幻读有啥区别?【英文标题】:What is the difference between Non-Repeatable Read and Phantom Read?不可重复读和幻读有什么区别? 【发布时间】:2012-06-18 02:42:19 【问题描述】:

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

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

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

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

【问题讨论】:

en.wikipedia.org/wiki/Isolation_(database_systems) 【参考方案1】:

From Wikipedia(其中有很好的详细示例):

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

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

简单例子:

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

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

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

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

【讨论】:

我不太明白这种语法的逻辑... NON-repeatable 读取发生在读取重复(并且获得不同的值)??!... @serhio "non-repeatable" 是指你可以读取一个值一次得到x作为结果,然后再次读取得到y作为结果,所以你不能重复(非-repeatable) 来自同一行的两个单独查询的相同结果,因为该行值在两次读取之间进行了更新。 两者听起来都一样 不同之处在于,当您执行 count(*) from table 并首先返回 42 然后返回 43 时,这不是不可重复读取,因为对于您第一次选择的 42 行,你第二次取回了相同的数据。因此,没有两次检索到不同值的行。但它仍然是一个幻读,因为你又得到了一个额外的行。因此,所有行值都是相同的,但您现在选择不同的行。 @sn.anurag 不同之处在于不可重复读取会为相同的逻辑行返回不同的值。 (例如,如果主键是employee_id,那么某个员工在两个结果中可能有不同的薪水。)幻像读取返回两组不同的行,但是对于出现在两组中的每一行,列值是一样。【参考方案2】:

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

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

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

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

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

【讨论】:

它与事务隔离级别和并发性有关。使用默认隔离级别,您不会得到脏读,并且在大多数情况下,您希望避免脏读。存在允许脏读的隔离级别或查询提示,在 某些 情况下,这是一个可接受的折衷方案,以实现更高的并发性,或者由于边缘情况是必要的,例如对正在进行的故障排除来自另一个连接的事务。脏读的想法没有通过你的“气味测试”很好,bc 作为一般规则,它们应该被避免,但确实有目的。 @phpAvenger 这里是 READ UNCOMMITTED 隔离级别的用例:总是有可能在选择和更新查询之间遇到死锁(解释为 here)。如果选择查询太复杂而无法创建覆盖索引,为了避免死锁,您将希望使用 READ UNCOMMITED 隔离级别,但存在遇到脏读的风险,但是您多久回滚一次事务以担心那些脏读不是永久的?! @petrica.martinescu 脏读引起的问题不仅仅是事务是否回滚。脏读可能会返回非常不准确的结果,具体取决于待处理事务中的数据是如何被修改的。想象一个执行一系列删除、更新和/或插入的事务。如果您在该事务的中间使用“未提交读取”读取数据,则它是不完整的。快照隔离级别(在 SQL Server 中)是读取未提交的更好选择。在生产系统中读取未提交隔离级别的有效用例在 IMO 中很少见。 @DiponRoy 好问题。如果使用可重复读取 (RR) 隔离实现的锁定应防止在已选择的行上发生删除。多年来,我看到了 2 个 iso 级别的不同定义,主要是说 phantom 是返回的集合/# 行中的更改,而 RR 是正在更改的同一行。我刚刚检查了更新的 MS SQL 文档说删除会导致非 RR (docs.microsoft.com/en-us/sql/odbc/reference/develop-app/…) 所以我认为在 RR 类别中分组删除也是安全的 @anir 是的,插入和删除包含在脏读中。示例:开始一个事务,在连接 a 上插入 100 个发票行中的 2 个,现在连接 b 在提交 trx 之前和添加其他 98 个行之前读取这 2 个行,因此不包括发票的所有信息。这将是涉及插入的脏读。【参考方案3】:

Non-Repeatable Read 异常如下所示:

    Alice 和 Bob 启动两个数据库事务。 Bob 读取帖子记录,标题列值为 Transactions。 Alice 将给定帖子记录的标题修改为 ACID 的值。 Alice 提交她的数据库事务。 如果 Bob 重新读取帖子记录,他将观察到此表行的不同版本。

幻读异常可能发生如下:

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

因此,Non-Repeatable Read 适用于单行,而Phantom Read 是关于满足给定查询过滤条件的一系列记录。 p>

【讨论】:

可以Phantom Read contian 多个non-repeatable reads吗? 这些异常之间没有包含操作。前者是关于范围扫描,而后者是关于单个记录。【参考方案4】:

Read phenomena

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

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

我们还可以观察到,在大多数用例中,更新可能是比实际插入或删除更频繁的工作(在这种情况下,不可重复读取的危险仅存在 - 幻象在这些情况下无法读取)。这就是为什么 UPDATES 的处理方式与 INSERT-DELETE 不同,由此产生的异常也有不同的名称。

还有与处理 INSERT-DELETE 相关的额外处理成本,而不仅仅是处理 UPDATE。

不同isolation levels的好处

READ_UNCOMMITTED 不会阻止任何事情。这是零 隔离级别 READ_COMMITTED 只防止一个,即脏读 REPEATABLE_READ 可防止两个异常情况:脏读和 不可重复读 SERIALIZABLE 可防止所有三种异常情况:脏读、 不可重复读和幻读

那为什么不一直设置事务 SERIALIZABLE 呢?好吧,上面问题的答案是:SERIALIZABLE 设置使事务非常,这也是我们不想要的。

实际上交易时间消耗是在以下比率:

SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED > READ_UNCOMMITTED

所以 READ_UNCOMMITTED 设置是最快的

总结

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

请注意,默认情况下数据库可能具有 REPEATABLE_READ 设置。管理员和架构师可能倾向于选择此设置作为默认设置,以展示更好的平台性能。

【讨论】:

UPDATE 或 DELETE 都可以用于不可重复读取,还是仅是 UPDATE? UPDATE 或 DELETE 都可以用于不可重复读取 其实我们可以总结一下,平均而言,同一个数据库的另一个事务执行的随机DELETE语句对当前事务造成不可重复读取的概率非常低。但是相同的删除语句有 100% 的机会导致当前事务的幻读。这么看,如果逐字逐句,我的写作有点错误。但是,嘿,我故意这样写是为了让读者更清楚。 +1 简单易懂的解释。但是我认为大多数数据库( oracle , mysql )的默认隔离级别为 Read Committed 并且可能 postgress 使用默认的 repeatable_read @akila - 我在撒谎。 ;-) 就像我已经提到的那样。 :-) 我说的是边界情况。【参考方案5】:

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

【讨论】:

要实现可重复读取或可序列化,不需要使用行锁。【参考方案6】:

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

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

【讨论】:

我不认为幻读的解释是正确的。即使非提交数据永远不可见,您也可以获得幻读。请参阅 Wikipedia 上的示例(在上面的 cmets 中链接)。【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

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

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

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

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

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

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

【讨论】:

以上是关于不可重复读和幻读有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

不可重复读和幻读的区别

数据库的脏读不可重复读和幻读区别

数据库的脏读不可重复读和幻读区别

关于数据库事务中脏读不可重复读和幻读的理解

什么是脏读,不可重复读,幻读

在 MySQL 中是如何通过 MVCC 机制来解决不可重复读和幻读问题的?