由于读已提交事务中数据视图在每个语句之间都在变化,即使是单个语句在发生写冲突时也可能无法限制在语句的快照范围内,因此使用读已提交事务来强制执行关于数据完整性的业务规则非常困难。
虽然可重复读事务在整个执行过程中都有稳定的数据视图,但在使用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
)开始时冻结的,因此有可能在快照冻结之前显式获取锁。
如果您在文档中发现任何不正确、与您对特定功能的使用经验不符或需要进一步澄清的内容,请使用 此表格 报告文档问题。