Mat和Erwin都是正确的,我只是添加另一个答案,以用不适合发表评论的方式进一步扩展他们所说的内容。由于他们的回答似乎并不能使所有人满意,因此有人建议应咨询PostgreSQL开发人员,我是其中的一员,我将详细说明。
这里的重点是,在SQL标准下,在以事务READ COMMITTED
隔离级别运行的事务中,限制是未提交的事务的工作必须不可见。当已提交事务的工作可见时,将取决于实现。您所指出的是两种产品选择实施该方法的差异。两种实现都不会违反该标准的要求。
这是在PostgreSQL中发生的详细信息:
S1-1运行(删除1行)
旧行留在原处,因为S1可能仍会回滚,但是S1现在对该行持有锁,以便任何其他尝试修改该行的会话都将等待以查看S1是提交还是回滚。除非尝试使用或锁定表,否则对表的任何读取仍然可以看到旧行。SELECT FOR UPDATE
SELECT FOR SHARE
S2-1运行(但由于S1具有写锁定而被阻止)
现在,S2必须等待才能看到S1的结果。如果S1将回滚而不是提交,则S2将删除该行。请注意,如果S1在回滚之前插入了新版本,则从任何其他事务的角度来看,新版本都不会存在,从任何其他事务的角度来看,旧版本也不会被删除。
S1-2运行(插入1行)
此行独立于旧行。如果存在id = 1的行的更新,则旧版本和新版本将相关,并且S2可以在该行的未阻塞状态下删除该行的更新版本。新行恰好具有与过去存在的某行相同的值,因此与该行的更新版本不相同。
S1-3运行,释放写锁定
因此,S1的更改得以保留。一行不见了。已添加一行。
S2-1运行,现在它可以获得锁。但是报告删除了0行。嗯?
在内部发生的是,如果有更新,则存在从行的一个版本到同一行的下一个版本的指针。如果该行被删除,则没有下一个版本。当READ COMMITTED
事务在发生写冲突时从块中唤醒时,它将遵循该更新链的末尾。如果该行尚未删除,并且仍然满足查询的选择条件,则将对其进行处理。该行已被删除,因此S2的查询继续进行。
S2在扫描表期间可能会也可能不会到达新行。如果是这样,它将看到新行是在S2的DELETE
语句开始之后创建的,因此它不是可见行集合的一部分。
如果PostgreSQL从头开始使用新快照重新启动S2的整个DELETE语句,则其行为与SQL Server相同。由于性能原因,PostgreSQL社区未选择这样做。在这种简单的情况下,您永远不会注意到性能上的差异,但是如果DELETE
在阻塞时您排成一千万行,那么您肯定会注意到。PostgreSQL在这里选择了性能,这是折衷方案,因为更快的版本仍然符合标准的要求。
S2-2运行,报告唯一的键约束冲突
当然,该行已经存在。这是图片中最令人惊讶的部分。
尽管这里有一些令人惊讶的行为,但是所有内容都符合SQL标准,并且在该标准的“特定于实现”的范围内。如果您假设所有实现中都会存在其他实现的行为,那肯定会令人惊讶,但是PostgreSQL竭尽全力避免READ COMMITTED
隔离级别的序列化失败,并允许某些与其他产品不同的行为来实现这一点。
现在,我个人并不喜欢任何产品实现中的READ COMMITTED
事务隔离级别。从交易的角度来看,它们都允许种族条件产生令人惊讶的行为。一旦某人习惯了一种产品所允许的怪异行为,他们就会倾向于认为“正常”,而另一种产品所选择的权衡取舍。但是,每种产品都必须对未实际实现为的任何模式进行某种折衷。PostgreSQL开发人员选择划清界线的地方是最大程度地减少阻塞(读取不阻塞写入,写入不阻塞读取)并最小化序列化失败的机会。SERIALIZABLE
READ COMMITTED
该标准要求将SERIALIZABLE
事务作为默认值,但是大多数产品不这样做,因为这会导致较宽松的事务隔离级别造成性能下降。选择某些产品甚至不提供真正可序列化的事务SERIALIZABLE
-最著名的是Oracle和9.1之前的PostgreSQL版本。但是使用真正的SERIALIZABLE
事务是避免竞争条件产生令人惊讶的影响的唯一方法,并且SERIALIZABLE
交易始终必须要么阻塞以避免竞争条件,要么回滚某些事务以避免发展的竞争条件。SERIALIZABLE
事务的最常见实现是严格的两阶段锁定(S2PL),它同时具有阻塞和序列化失败(以死锁的形式)。
全面披露:我与麻省理工学院的Dan Ports合作,使用一种名为“可序列化快照隔离”的新技术,将真正可序列化的事务添加到PostgreSQL 9.1版中。