3

隔离级别是否仅适用于 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 的更改,要么不包含。更新也是如此吗?

以下是一个示例场景:

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、MySql 和 PostgreSQL?

4

2 回答 2

7

假设您打算展示许多 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 - 大多数数据库甚至不支持它)线程 B 将不匹配任何行并且没有效果。

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

在任何健全的数据库中,只有两个更新中的一个会成功 - 第二个将匹配零行或因序列化失败而中止,具体取决于隔离级别和数据库实现。即使在带有 InnoDB 的 MySQL 中也是如此(请参阅此答案中的详细说明和演示),至少在 5.5 中也是如此。如果您使用的是 MyISAM,那么正确性和可靠性显然对您来说不是大问题;-)

我不知道有任何数据库将不同的隔离规则应用于UPDATEs vs SELECTs。毕竟, an的子句、子查询等UPDATE需要与 a 相同的隔离保证。s 可以死锁而s 不能(在 PostgreSQL 中;显然他们可以在 MySQL+InnoDB 中)。与s 不同的是, s 在隔离模式下会遭受序列化失败- 但它们具有相同的可见性规则。WHERESELECTUPDATESELECTSELECTUPDATESERIALIZABLE

PostgreSQL 关于并发控制的文档很好地解释了这一点。

于 2012-11-18T02:28:43.687 回答
1

在 Oracle 中,存在语句级一致性

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

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

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

更新工作类似,但涉及一些额外的工作。所有更新/删除都以具有标准时间点一致性的标准 SELECT 开始,但在CURRENT MODE. 这是因为必须修改的块版本是最后一个。此外,最后一个也是包含有关块上当前锁的信息的那个。Tom Kyte 对 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重新启动其查询,如另一个 askTom 线程中所述:

结果集是一致的——但它很可能在重新启动时是一致的。

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

于 2012-11-19T12:05:29.213 回答