不可重复读和幻读有什么区别?
我已阅读 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
另一个疑问是,在上面的例子中,应该使用哪个隔离级别?为什么?
From Wikipedia(其中有很好的详细示例):
发生不可重复读取时,在事务过程中,行被检索两次并且行内的值在读取之间不同。
和
当在事务过程中执行两个相同的查询,并且第二个查询返回的行集合与第一个不同时,就会发生幻读。
简单的例子:
用户 A 两次运行相同的查询。
在这两者之间,用户 B 运行事务并提交。
不可重复读:用户A第二次查询的A行值不同。
幻读:查询中的所有行之前和之后的值都相同,但是选择的行不同(因为B已经删除或插入了一些)。示例:从表中选择 sum(x);如果行已被添加或删除,即使受影响的行本身没有被更新,也会返回不同的结果。
在上面的例子中,要使用哪个隔离级别?
您需要什么隔离级别取决于您的应用程序。 “更好”的隔离级别(例如降低并发性)的成本很高。
在您的示例中,您不会进行幻读,因为您仅从单行中选择(由主键标识)。您可以进行不可重复读取,因此如果这是一个问题,您可能需要一个隔离级别来防止这种情况发生。在 Oracle 中,事务 A 也可以发出 SELECT FOR UPDATE,然后事务 B 在 A 完成之前不能更改行。
我喜欢考虑的一个简单方法是:
不可重复读取和幻读都与来自不同事务的数据修改操作有关,这些操作在事务开始后提交,然后由事务读取。
不可重复读取是指您的事务从另一个事务读取已提交的更新。现在,同一行的值与事务开始时的值不同。
幻读是相似的,但是当从另一个事务中读取已提交的 INSERTS 和/或 DELETES 时。自您开始交易以来,有新的行或已消失的行。
脏读类似于不可重复读和幻读,但与读取 UNCOMMITTED 数据有关,发生在读取另一个事务的 UPDATE、INSERT 或 DELETE,而另一个事务尚未提交数据时。它正在读取“进行中”的数据,这些数据可能不完整,并且可能永远不会真正提交。
不可重复读取异常如下所示:
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
吗?
阅读现象
脏读:从另一个事务中读取 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 设置。管理员和架构师可能倾向于选择此设置作为默认设置,以展示更好的平台性能。
这两种隔离级别在实现上是有区别的。
对于“不可重复读”,需要行锁。
对于“幻读”,需要范围锁,甚至是表-锁定。
我们可以通过使用 two-phase-locking 协议来实现这两个级别。
在具有不可重复读取的系统中,事务 A 的第二次查询的结果将反映事务 B 中的更新——它将看到新的金额。
在允许幻读的系统中,如果事务 B 插入 ID = 1 的新行,则事务 A 将在执行第二个查询时看到新行;即幻读是不可重复读的一种特殊情况。
公认的答案主要表明,两者之间的所谓区别实际上根本不重要。
如果“一行被检索两次并且行内的值在读取之间不同”,那么它们不是同一行(在正确的 RDB 中不是同一个元组),那么根据定义,实际上也是“集合第二个查询返回的行与第一个不同”。
至于“应该使用哪种隔离级别”的问题,您的数据对某人、某处越重要,Serializable 就越是您唯一合理的选择。
我认为不可重复读取和幻读之间存在一些差异。
Non-repeateable 表示有两个事务 A 和 B。如果 B 可以注意到 A 的修改,那么可能会发生脏读,所以我们让 B 在 A 提交后注意到 A 的修改。
有一个新问题:我们让B在A提交后注意到A的修改,这意味着A修改了B持有的行的值,有时B会再次读取该行,所以B会得到与我们第一次不同的新值得到,我们称之为不可重复,为了处理这个问题,我们让B在B开始时记住一些东西(因为我不知道会记住什么)。
让我们考虑一下新的解决方案,我们可以注意到还有新的问题,因为我们让 B 记住了一些东西,所以无论 A 发生什么,B 都不会受到影响,但是如果 B 想向表和 B 插入一些数据检查表以确保没有记录,但该数据已被A插入,因此可能会出现一些错误。我们称之为幻读。
不可重复读是一个隔离级别,幻读(读取其他事务提交的值)是一个概念(读类型,例如脏读或快照读)。不可重复读隔离级别允许幻读,但不允许脏读或快照读。
不可重复读取和幻读都来自一个事务 T1,该事务 T1 看到另一个事务 T2 在 T1 完成之前提交的更改。不同之处在于不可重复读取会为同一逻辑行返回不同的值。 (例如,如果主键是employee_id,那么某个员工在两个结果中可能有不同的薪水。)幻像读取返回两组不同的行,但是对于出现在两组中的每一行,列值是相同的。
不定期副业成功案例分享
count(*) from table
并首先返回42
然后返回43
时,这不是不可重复读取,因为对于您第一次选择的 42 行,第二次返回相同的数据时间。因此,没有两次检索到不同值的行。但它仍然是一个幻读,因为你又得到了一个额外的行。因此,所有行值都是相同的,但您现在选择不同的行。 @sn.anurag