13.4. 应用程序级别的 Data Consistency Checks#
使用 Read Committed 事务很难强制有关数据完整性的业务规则,因为每次语句都会改变数据的视图,即使单个语句在发生写入冲突时也可能不会限制自身仅限于该语句的快照。
虽然可重复读事务在其执行过程中对数据有稳定的视图,但使用MVCC快照进行数据一致性检查时有一个微妙的问题,涉及到称为读/写冲突的问题。如果一个事务写入数据,而一个并发事务尝试读取相同的数据(无论是在写入之前还是之后),它都无法看到另一个事务的工作。然后,无论哪个事务先启动或先提交,读事务似乎都先执行。如果仅此而已,则没有问题,但如果读事务还写入数据,而并发事务读取该数据,则现在有一个事务似乎在前面提到的任何一个事务之前运行。如果似乎最后执行的事务实际上先提交,则很容易在事务执行顺序图中出现循环。当出现这样的循环时,如果不提供一些帮助,完整性检查将无法正常工作。
如第 13.2.3 节中所述,可串行化事务只是可重复读事务,它增加了对危险读/写冲突模式的非阻塞监视。当检测到可能导致执行顺序明显循环的模式时,将回滚涉及的事务之一以打破循环。
13.4.1. 使用可串行化事务强制一致性#
如果可串行化事务隔离级别用于所有写入和所有需要数据一致性视图的读取,则无需其他工作来确保一致性。从其他环境编写的软件,如果使用可串行化事务来确保一致性,那么在PostgreSQL中应该“正常工作”。
使用此技术时,如果应用程序软件通过一个框架自动重试因序列化失败而回滚的事务,这将避免给应用程序程序员造成不必要的负担。将default_transaction_isolation
设置为serializable
可能是个好主意。明智的做法还包括采取一些措施来确保不会在触发器中通过对事务隔离级别的检查而无意中或为了破坏完整性检查而使用其他事务隔离级别。
有关性能建议,请参见第 13.2.3 节。
警告:可序列化事务和数据复制
使用可序列化事务的这种完整性保护级别尚未扩展到热备用模式(第 27.4 节)或逻辑副本。因此,使用热备用或逻辑复制的用户可能希望在主服务器上使用可重复读和显式锁定。
13.4.2. 通过显式阻塞锁强制一致性#
当可能发生不可序列化的写入时,为了确保行的当前有效性并防止其受到并发更新的保护,必须使用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
)开始时冻结,因此可以在快照冻结之前显式获取锁。