隔离级别仅适用于SELECTS而不是更新吗?

时间:2012-11-17 14:35:52

标签: mysql database oracle postgresql

隔离级别仅适用于SELECTS而非UPDATES吗?

演示了SELECTS的不同隔离行为的场景

1) 0:00 Thread A runs a query that returns 1000 rows that takes 5 minutes to complete
2) 0:02 Thread B runs a query that returns the same 1000 rows
3) 0:05 Thread A updates the last 1 rows in this result set and commits them
4) 0:07 Thread B's query returns* 

根据隔离级别,#4中的结果集将包含线程A的更改,或者不包含。 UPDATES也是如此吗?

以下是一个示例场景:

Thread A: UPDATE ... WHERE primary_key = 1234 AND version = 5
Thread B: UPDATE ... WHERE primary_key = 1234 AND version = 5

如果线程A和线程B同时进入其事务,并且线程B在线程A之后执行其更新,则线程B的更新将失败还是它将“看到”具有版本5的记录并因此成功?

它取决于数据库吗?例如Oracle vs MySql vs PostgreSQL?

2 个答案:

答案 0 :(得分:6)

假设您打算显示许多ORM使用的“乐观锁定”模式,例如:

Thread A: UPDATE ... SET ..., version = 6 WHERE primary_key = 1234 AND version = 5
Thread B: UPDATE ... SET ..., version = 6 primary_key = 1234 AND version = 5

然后在所有合理的隔离级别(我不是100%确定READ UNCOMMITTED - 大多数DB甚至不支持它)线程B将不匹配任何行并且没有效果。

在PostgreSQL中,例如,线程B最初将匹配与A相同的行,但在行更新锁定时阻塞,直到线程A提交或回滚。此时它将重新检查条件并发现如果线程A已提交则不再匹配,因此它将不执行任何操作。行锁定意味着序列化冲突永远不会在这种特殊情况下发挥作用。

在任何理智的数据库中,只有两个更新中的一个会成功 - 第二个将匹配零行或中止序列化失败,具体取决于隔离级别和数据库实现。即使在具有InnoDB(see detailed explanation and demo in this answer)的MySQL中,至少在5.5中也是如此。如果你正在使用MyISAM,那么正确性和可靠性显然不是你的大问题; - )

我不知道有任何数据库对UPDATE s vs SELECT应用不同的隔离规则。毕竟,UPDATE需要与WHERE子句,子查询等相同的隔离保证,作为SELECTUPDATE可以解决SELECT不能解决的问题(在PostgreSQL中;显然它们可以在MySQL + InnoDB中)。与SELECT不同,UPDATESERIALIZABLE隔离模式下会受到序列化失败的影响 - 但它们具有相同的可见性规则。

PostgreSQL关于concurrency control的文档很好地解释了这一点。

答案 1 :(得分:1)

在Oracle中,有一个statement-level consistency

  

Oracle数据库始终强制执行语句级读取一致性,这可确保单个查询返回的数据已提交并且与单个时间点保持一致。

这意味着您的SELECT示例在Oracle中不会像这样工作:线程B将返回select中的结果,因为它们位于语句的开头。这意味着Oracle可能会像查询开始时那样从撤消数据中重新创建过去的块,这样长时间运行的查询的结果才有意义。第(3)点中的更新所做的更改将不会出现在结果中。

选择查询在开始后不会看到所做的事务更改,即使它们已提交。

更新工作类似但涉及一些额外的工作。所有更新/删除都以具有标准时间点一致性的标准SELECT开始,但在CURRENT MODE中询问这些块。这是因为必须修改的块版本是最后一个版本。此外,最后一个也是包含有关块上当前锁的信息的那个。 Tom Kyte有一个nice analogy用于DML(删除和更新的工作方式相同):

  

想一想像这样处理的删除:

for x in ( select rowid from emp )   --- CONSISTENT GETS
loop
   delete from emp where rowid = x.rowid;  --- CURRENT MODE GETS
end loop;

现在,如果我们通过更新替换SELECT,会在您的场景中发生什么?

首先,如果行仍然被锁定(第(3)点中的事务尚未提交),则第(4)点中的更新将等待 - 直到(3)提交或回滚。

如果事务已提交,并且您处于SERIALIZABLE事务隔离状态,则当然会出现错误。我们不想修改自事务开始以来已更改的数据(因为这些更改是不可见的)。

READ COMMITED中,有一个有趣但不直观的发展。当Oracle获取已修改的行时,它会意识到在查询开始后数据已被修改。 Oracle现在无法处理更新,因为这不一致(此外,这意味着丢失更新的情况)。因此Oracle 重新启动其查询,如此other askTom thread

中所述
  

结果集是一致的 - 但从重启时间开始可能会保持一致。

我们得到第二个“选择”,这次(希望)以一致的方式获得所有行。由于锁是逐行放置的,因此第一遍中找到的所有行仍然可用(它们不能被第一遍和第二遍之间的另一个事务修改)。