13.4. 应用程序级别的数据一致性检查

使用读取已提交的事务来执行关于数据完整性的业务规则非常困难,因为数据视图随每个语句而变化,并且即使发生写冲突,即使是单个语句也可能不会将自身限制为该语句的快照。

尽管“可重复读取”事务在整个执行过程中都具有稳定的数据视图,但是使用 MVCC 快照进行数据一致性检查存在一个细微的问题,涉及到读/写冲突。如果一个事务写入数据,而一个并发事务尝试读取相同的数据(无论是在写入之前还是之后),则它看不到另一事务的工作。然后,无论先启动哪个还是先提交哪个,Reader 都似乎先执行了。如果可以做到这一点,那没有问题,但是如果读取器也写入并发事务读取的数据,则现在看来有一个事务在前面提到的任何一个事务之前都已运行。如果看似最后执行的事务实际上首先提交,则周期很容易出现在事务执行 Sequences 的图表中。当出现这样的循环时,如果没有一些帮助,完整性检查将无法正确进行。

Section 13.2.3中所述,可序列化事务只是可重复读取事务,它添加了对危险的读写冲突模式的无阻塞监视。当检测到可能导致明显执行 Sequences 中的周期的模式时,所涉及的事务之一将回滚以破坏周期。

13 .4.1. 通过可序列化的事务加强一致性

如果将可序列化事务隔离级别用于需要数据视图一致的所有写入和所有读取,则无需进行其他工作即可确保一致性。在这方面,其他环境中的软件被编写为使用可序列化的事务来确保一致性,这在 PostgreSQL 中应“正常工作”。

使用此技术时,如果应用程序软件通过自动重试因序列化失败而回滚的事务的框架,则可以避免给应用程序程序员带来不必要的负担。将default_transaction_isolation设置为serializable可能是一个好主意。明智的是,通过对触发器中的事务隔离级别进行检查,以确保不使用其他任何事务隔离级别,或者无意或破坏完整性检查,这也是明智的。

有关性能建议,请参见Section 13.2.3

Warning

使用可序列化事务的完整性保护级别尚未扩展到热备用模式(Section 26.5)。因此,那些使用热备用的用户可能希望在主机上使用重复读取和显式锁定。

13 .4.2. 使用显式阻止锁加强一致性

如果可能进行不可序列化的写入,则必须使用SELECT FOR UPDATESELECT FOR SHARE或适当的LOCK TABLE语句来确保行的当前有效性并防止其并发更新。 (SELECT FOR UPDATESELECT FOR SHARE仅锁定返回的行以防止并发更新,而LOCK TABLE锁定整个表.)从其他环境将应用程序移植到 PostgreSQL 时,应考虑到这一点。

对于那些从其他环境进行转换的人来说,值得注意的是SELECT FOR UPDATE不能确保并发事务不会更新或删除所选行。为此,即使不需要更改任何值,您也必须实际上更新该行。 SELECT FOR UPDATE 暂时阻止其他事务获取同一锁或执行UPDATEDELETE,这会影响锁定的行,但是持有此锁的事务一旦提交或回滚,除非实际的UPDATE,否则被阻止的事务将 continue 执行冲突操作。持有锁时执行了该行的。

对于不可序列化的 MVCC,全局有效性检查需要额外的考虑。例如,当两个表都被主动更新时,银行应用程序可能希望检查一个表中的所有贷项总和等于另一表中的借项总和。在 Read Committed 模式下,比较两个连续的SELECT sum(...)命令的结果将无法可靠地进行,因为第二个查询可能会包含第一个查询未计算的事务结果。在单个可重复读取事务中执行两个总和将仅给出可重复读取事务开始之前提交的事务的效果的准确图片-但人们可能会合理地想知道,答案在交付之时是否仍然有用。如果可重复读事务本身在尝试进行一致性检查之前应用了一些更改,则该检查的用途变得更具争议性,因为现在它包括一些但不是全部的事务开始后更改。在这种情况下,细心的人可能希望锁定检查所需的所有表,以便对当前现实有一个无可争辩的印象。 SHARE模式(或更高版本)的锁定可确保锁定表中没有未提交的更改(当前事务除外)。

还要注意,如果有人依靠显式锁定来防止并发更改,则应该使用“读已提交”模式,或者在“可重复读”模式下,在执行查询之前请小心获取锁。由可重复读取事务获得的锁保证了没有其他修改表的事务仍在运行,但是如果事务看到的快照早于获得该锁,则它可能早于表中某些当前提交的更改。可重复读取事务的快照实际上是在其第一个查询或数据修改命令(SELECTINSERTUPDATEDELETE)开始时冻结的,因此可以在冻结快照之前显式获取锁。