ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我已阅读 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

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


d
dade

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

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

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

简单的例子:

用户 A 两次运行相同的查询。

在这两者之间,用户 B 运行事务并提交。

不可重复读:用户A第二次查询的A行值不同。

幻读:查询中的所有行之前和之后的值都相同,但是选择的行不同(因为B已经删除或插入了一些)。示例:从表中选择 sum(x);如果行已被添加或删除,即使受影响的行本身没有被更新,也会返回不同的结果。

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

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

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


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

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

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

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

幻读是相似的,但是当从另一个事务中读取已提交的 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 个行,因此不包括发票的所有信息。这将是涉及插入的脏读。
V
Vlad Mihalcea

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

https://i.stack.imgur.com/iPI0C.png

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

幻读异常可能发生如下:

https://i.stack.imgur.com/aCtew.png

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

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


Phantom Read 可以包含多个 non-repeatable reads 吗?
这些异常之间没有包含操作。前者是关于范围扫描,而后者是关于单个记录。
当 Bob 尝试根据他上次读取的值更新值时,不可重复读取不会导致丢失更新问题吗?
S
Subhadeep Ray

阅读现象

脏读:从另一个事务中读取 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 设置。管理员和架构师可能倾向于选择此设置作为默认设置,以展示更好的平台性能。


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

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


要实现可重复读取或可序列化,不需要使用行锁定。
J
Jeffrey Kemp

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

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


我不认为幻读的解释是正确的。即使非提交数据永远不可见,您也可以获得幻读。请参阅 Wikipedia 上的示例(链接在上面的评论中)。
E
Erwin Smout

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

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

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


B
BartoszKP

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

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

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

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


s
sn.anurag

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


D
Don Smith

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