由于使用 Read Committed 事务时的 Observed Rows 视图会随着每条语句发生变化,而且如果发生写冲突,即使单个语句也可能无法限制在语句的快照中,因此使用 Read Committed 事务强制执行有关数据完整性的业务规则极其困难。
尽管在整个执行过程中,Repeatable Read 事务会将 Observed Rows 视图保持稳定,但在使用多版本并发控制用于数据一致性检查时,存在一个细微问题,涉及到被称为读/写冲突的问题。如果一个事务写入了数据,并且一个并行事务尝试读取同一数据(无论是在写之前还是之后),该事务看不到另一个事务的工作。然后,读者看来先执行了,而与哪个事务先启动或哪个事务先提交无关。如果到此为止,则没有问题,但如果读者还写入了数据,而该数据可由并行事务读取,则现在会有一个事务,而该事务看来已在上述任一事务之前运行。如果看来最后执行的事务实际上是最先提交的,则在一个表示事务执行顺序的图中非常容易出现一个循环。当出现此类循环时,如果不提供一些帮助,完整性检查将无法正常工作。
正如第 13.2.3 节中提到的,可序列化事务只是 Repeatable Read 事务,增加了对危险的读/写冲突模式的无阻塞监控。当检测到一个可能在明显的执行顺序中导致循环的模式时,涉及到的一个事务会回滚以中断该循环。
如果对所有写入和所有需要一致的 Observed Rows 视图的读取使用可序列化事务隔离级别,则无需其他 effort 即可确保一致性。出于确保一致性的目的而编写以使用可序列化事务的其他环境中的软件在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
。
在不可串行化下,全局有效性检查要求额外的思考多版本并发控制. 例如,当两个表均处于活动更新状态时,银行应用程序希望检查一个表中所有贷项总和等于另一个表中所有借项总和。在读已提交模式下比较两条连续的 SELECT sum(...)
命令的结果不可靠,因为第二个查询可能包括第一个查询未计算的事务结果。在单个可重复读取事务中执行这两个求和操作只会准确反映可重复读取事务开始前己提交事务的影响,但人们可能会合理地怀疑,在答案传送出来之前,它依然是否相关。如果可重复读取事务在尝试执行一致性检查之前本身已经应用了一些更改,那么该检查的实用性就更加值得商榷,因为现在它只包含了部分事务启动后的更改,而不是全部。在这样的情况下,仔细的人可能希望锁住检查所需的所有表,以获取当前现实情况的无可争议图像。 SHARE
模式(或更高)锁保证锁定的表中没有未提交的更改,除了当前事务的更改之外。
还要注意,如果有人依赖显式锁定来防止并发更改,那么应该使用读已提交模式,或者在可重复读取模式下在执行查询之前应谨慎获取锁。可重复读取事务获得的锁保证没有其他修改表的正在运行的事务,但如果事务看到的快照早于获得锁,那么它可能早于表中一些现在已提交的更改。可重复读取事务的快照实际上是在其第一个查询或数据修改命令(SELECT
、INSERT
、UPDATE
、DELETE
或 MERGE
)开始时冻结的,因此可以在快照冻结之前显式获取锁。