由于数据的视图随着每个语句而变化,甚至在发生写入冲突时,单个语句也可能不限于该语句的快照,因此使用读已提交事务来强制执行有关数据完整性的业务规则非常困难。
虽然可重复读事务在其执行过程中具有稳定的数据视图,但在使用MVCC快照进行数据一致性检查时,存在一个微妙的问题,涉及所谓的读/写冲突。如果一个事务写入数据,并且一个并发事务尝试读取相同的数据(无论是在写入之前还是之后),它都无法看到另一个事务的工作。然后,读取器似乎先执行了,而不管哪个先开始或哪个先提交。如果仅此而已,则没有问题,但是如果读取器也写入了由并发事务读取的数据,则现在存在一个事务,该事务似乎在之前提到的任何事务之前运行。如果看起来最后执行的事务实际上先提交,则很容易在事务执行顺序的图中出现循环。当出现这样的循环时,如果没有一些帮助,完整性检查将无法正常工作。
如第 13.2.3 节所述,可序列化事务只是可重复读事务,它们增加了对危险的读/写冲突模式的非阻塞监控。当检测到可能导致执行顺序出现循环的模式时,将回滚其中一个涉及的事务以打破循环。
如果可序列化事务隔离级别用于所有写入和所有需要数据一致性视图的读取,则无需其他努力来确保一致性。其他环境中编写的,使用可序列化事务来确保一致性的软件,在这方面应该在 PostgreSQL 中“正常工作”。
当使用此技术时,如果应用程序软件通过一个自动重试因序列化失败而回滚的事务的框架,则将避免给应用程序程序员带来不必要的负担。将 default_transaction_isolation
设置为 serializable
可能是一个好主意。通过触发器中对事务隔离级别的检查,采取一些措施来确保不会无意或破坏完整性检查而使用其他事务隔离级别也是明智的。
有关性能建议,请参阅第 13.2.3 节。
使用可序列化事务的这种完整性保护级别尚未扩展到热备用模式(第 26.4 节)或逻辑副本。因此,使用热备用或逻辑复制的用户可能希望在主节点上使用可重复读和显式锁定。
当可能进行非可序列化写入时,为了确保行的当前有效性并保护其免受并发更新,必须使用 SELECT FOR UPDATE
、SELECT FOR SHARE
或适当的 LOCK TABLE
语句。(SELECT FOR UPDATE
和 SELECT FOR SHARE
仅锁定返回的行以防止并发更新,而 LOCK TABLE
则锁定整个表。)将应用程序从其他环境移植到 PostgreSQL 时应考虑到这一点。
对于那些从其他环境转换的人,还应注意 SELECT FOR UPDATE
不能确保并发事务不会更新或删除选定的行。要做到这一点,在 PostgreSQL 中,您实际上必须更新该行,即使不需要更改任何值。SELECT FOR UPDATE
临时阻止其他事务获取相同的锁或执行将影响锁定行的 UPDATE
或 DELETE
,但是一旦持有此锁的事务提交或回滚,除非在持有锁时执行了对该行的实际 UPDATE
,否则被阻止的事务将继续执行冲突的操作。
在非可序列化情况下,全局有效性检查需要额外考虑MVCC。例如,当两个表都在积极更新时,银行应用程序可能希望检查一个表中的所有贷方总和是否等于另一个表中的借方总和。比较两个连续的 SELECT sum(...)
命令的结果在读已提交模式下无法可靠地工作,因为第二个查询很可能会包括第一个查询未计算的事务的结果。在单个可重复读事务中执行两次求和将仅准确地显示在可重复读事务开始之前提交的事务的影响,但是人们可能会合理地怀疑,当交付答案时,该答案是否仍然相关。如果可重复读事务本身在尝试进行一致性检查之前应用了一些更改,则该检查的有用性变得更加值得商榷,因为它现在包含了一些但不包含所有事务开始后的更改。在这种情况下,谨慎的人可能希望锁定检查所需的所有表,以便获得当前现实的无可辩驳的画面。SHARE
模式(或更高模式)锁定保证锁定表中没有未提交的更改,除了当前事务的更改。
另请注意,如果依赖显式锁定来防止并发更改,则应使用读已提交模式,或者在可重复读模式下,应注意在执行查询之前获取锁。由可重复读事务获得的锁保证没有其他修改该表的事务仍在运行,但是如果事务看到的快照早于获取锁,则它可能早于表中一些现在已提交的更改。可重复读事务的快照实际上是在其第一个查询或数据修改命令(SELECT
、INSERT
、UPDATE
、DELETE
或 MERGE
)开始时冻结的,因此可以在快照冻结之前显式获取锁。
如果您在文档中发现任何不正确、与您使用特定功能的体验不符或需要进一步澄清的地方,请使用此表格报告文档问题。