Postgresql 中文操作指南
13.4. Data Consistency Checks at the Application Level #
使用读提交事务很难以强制执行有关数据完整性的业务规则,因为每次语句执行时,数据视图都会发生转换,即使发生写冲突,单个语句也可能不会局限于语句快照。
尽管可重复读事务在其整个执行过程中都具有稳定数据视图,但使用MVCC快照来进行数据一致性检查时存在一个微小的问题,其中与_read/write conflicts_中的某些事物有关。如果一个事务写入数据,并发事务尝试读取相同数据(在写入之前或之后),它无法看到其他事务的工作。然后读者似乎先执行了,无论哪个先开始,哪个先提交。如果仅此而已,则没问题,但如果读者还写入并发事务读取的数据,则现在有一个事务似乎在前面提到的任何一个事务之前运行。如果似乎最后执行的事务实际上先提交,那么在事务执行顺序的图中很容易出现循环。当出现这样的循环时,如果不采取一些帮助措施,则完整性检查将无法正常工作。
如 Section 13.2.3中所述,可串行化事务只是可重复读取事务,增加了对危险模式的读/写冲突的非阻塞监控。当检测到可能导致执行明显顺序形成循环的模式时,将回滚所涉及的事务之一以打破循环。
13.4.1. Enforcing Consistency with Serializable Transactions #
如果对所有写入和所有需要一致数据视图的读取使用可序列化事务隔离级别,则不需要进行其他努力就能确保一致性。从其他环境中编写的软件如果使用可序列化事务来确保一致性,则在PostgreSQL中应在这方面“正常工作”。
当使用此技术时,如果应用软件通过自动重试因序列化失败而回滚的事务的框架,将避免给应用程序员带来不必要的负担。将_default_transaction_isolation_设置为_serializable_可能是一个好主意。通过在触发器中检查事务隔离级别,采取一些措施来确保没有使用其他事务隔离级别,无论是出于无意还是为了破坏完整性检查,这也很明智。
请参见 Section 13.2.3以获取性能建议。
Warning: Serializable Transactions and Data Replication
使用可串行化事务的这种级别的完整性保护尚未扩展到热备用模式( Section 27.4)或逻辑副本。因此,使用热备用或逻辑复制的人可能希望在主服务器上使用可重复读取和显式锁定。
13.4.2. Enforcing Consistency with Explicit Blocking Locks #
当不可序列化写操作可能发生时,为了确保行的当前有效性并防止同时更新,必须使用 SELECT FOR UPDATE、SELECT FOR SHARE 或适当的 LOCK TABLE 语句。(SELECT FOR UPDATE 和 SELECT FOR SHARE 只锁定返回行以防止同时更新,而 LOCK TABLE 锁定整个表。)在将应用程序从其他环境移植到 PostgreSQL 中时,应该考虑到这一点。
对于从其他环境转换的人来说,另一件需要注意的事情是 SELECT FOR UPDATE 不能确保并发事务不会更新或删除选定行。要在 PostgreSQL 中执行此操作,必须实际更新该行,即使不需要更改任何值。SELECT FOR UPDATE temporarily blocks 其他事务获取相同的锁或执行 UPDATE 或 DELETE(这会影响锁定的行),但一旦持有此锁的事务提交或回滚,则阻塞事务将继续执行冲突操作,除非在持有锁时实际对行 UPDATE。
在不可序列化的 MVCC 下,全局有效性检查需要额外的考虑。例如,当两个表同时积极更新时,银行应用程序可能希望检查一个表中所有贷方的总金额是否等于另一个表中所有借方的总金额。在 Read Committed 模式下,比较两个连续的 SELECT sum(…) 命令的结果将不可靠,因为第二个查询很可能包含第一个查询未计算的事务的结果。在单个可重复读事务中执行这两个总计将准确描述只在可重复读事务开始之前提交的事务的影响——但人们可能合理地想知道,在传递答案时,答案是否仍然相关。如果可重复读事务本身在尝试进行一致性检查之前应用了一些更改,则检查的有效性将更具争议性,因为现在它包括了一部分但并不是所有事务开始后的更改。在这种情况下,谨慎的人可能希望锁定检查所需的所有表,以获得当前现实的无可争辩的描述。一个 SHARE 模式(或更高版本)锁定可以保证锁定表中没有未提交的更改,除了当前事务的更改。
另请注意,如果依赖显式锁定来防止并发更改,则应使用 Read Committed 模式,或者在可重复读模式下,在执行查询之前小心获取锁定。由可重复读事务获取的锁可确保没有修改表的其他事务仍在运行,但如果事务看到的快照早于获取锁,则该快照可能早于表中一些现已提交的更改。可重复读事务的快照实际上是在其第一个查询或数据修改命令的开始处冻结的(SELECT、INSERT、UPDATE、DELETE 或 MERGE),因此可以在快照冻结之前显式获取锁定。