13.4. 应用层数据完整性检查

考虑到数据完整性,使用读已提交事务很难强制执行业务规则,因为数据视图在每个语句间是转移的。 甚至于,如果发生写入冲突,单个语句也不能把自己限制到语句的快照。

尽管可重复读事务在执行期间有稳定的数据视图, 使用MVCC快照进行数据一致性检查有一个微妙的问题,涉及某个被称为读/写冲突的东西。 如果一个事务写入数据,并且另一个并发事务尝试读取同一数据(无论写之前还是之后), 它不能看到其他事务的工作。读者似乎首先被执行,无论哪个先被启动或先被提交。 如果仅仅是这样,这是没有问题的,但如果读者也写由并发事务读取的数据, 则现在似乎有一个事务运行在前面已提到的两个事务之前。 如果似乎是最后被执行的事务实际上是首先提交,在事务执行顺序图中就很容易出现一个循环。 当这样一个循环出现时,完整性检查在没有某些帮助的情况下将不能正常工作。

正如第 13.2.3 节提及到的,可串行化事务只不过是在可重复读事务上添加了 读/写冲突的危险模式的非阻塞监视。当监视到有可能导致明显的执行顺序的循环时,其中的一个事务会被回滚以打破这个循环。

13.4.1. 可串行化事务的强制一致性

如果串行化事务隔离级别被用于需要数据一致性视图的所有写和所有读,不需要其它努力就可以确保一致性。 来自其他环境的使用串行化事务确保一致性的软件,在PostgreSQL这方面应该"只是工作"

当使用这种技术时,如果应用软件经过框架, 并且这个框架会自动重试由于可串行化失败而导致回滚的事务, 那么可以避免产生不必要的负担。 设置default_transaction_isolationserializable也许是一个很好的主意。 采取一些措施确保没有使用其它事务隔离级别可能也是明智的,或者是无意的或者破坏完整性的检查, 通过在触发器中的事务隔离级别检查。

参见第 13.2.3 节获取性能建议。

警告

使用串行化事务的完整性保护级别还没有延伸到热备模式(第 25.5 节)。 因此,那些使用热备的可能要使用可重复读,并且在主库上明确封锁。

13.4.2. 明确阻塞锁的强制一致性

当可能进行非串行化写入时,要保证一行的当前有效性,并避免其被并发更新, 我们必须使用SELECT FOR UPDATE, SELECT FOR SHARE或者合适的LOCK TABLE语句。 (SELECT FOR UPDATESELECT FOR SHARE 只是锁住返回的行以防止对这些行的并发更新,而LOCK TABLE会锁住整个表。) 当从其它环境向PostgreSQL移植时,一定要把这些问题考虑进去。

从其它环境转换时还要注意的事实是:SELECT FOR UPDATE 不能保证并发事务不更新或删除已选择的行。 如果想在PostgreSQL中做到这一点,你必须实际更新这一行,即使没有值需要被修改。 SELECT FOR UPDATE 暂时阻塞其它事务获得相同的锁,执行可能影响封锁行的UPDATEDELETE命令, 但是一旦持有这个锁的事务提交或者回滚,被阻塞的事务将继续做冲突的操作, 除非当锁被持有的时候,实际执行了这一行的UPDATE

在非串行化MVCC下,需要额外考虑全局有效性检查。 比如,一个银行应用可能会希望检查一个表中的所有扣款总和等于另外一个表中的加款总和, 同时两个表还会被活跃地更新。在读已提交模式下比较两个连续的SELECT sum(...)命令的结果是不可靠的, 因为第二个查询很可能会包含第一个没计算的事务提交的结果。 在单个的可重复读事务里进行两个求和则给出在可重复读事务开始之前提交的所有事务产生的精确的结果— 但我们还是会合理地置疑在结果提交的时候,它们是否还相关。 如果可重复读事务本身在试图做一致性检查之前进行了某些变更,那么检查的有用性就更加值得讨论了, 因为现在它包含了一些(但不是全部)事务开始后的变化。在这种情况下, 一个仔细的人会希望锁住所有需要检查的表,这样才能获得一个无可置疑的当前现状的图像。 一个SHARE模式(或者更高级)的锁保证在被封锁表中除了当前事务之外,没有未提交的更新。

还要注意如果我们依赖明确封锁来避免并发更新,那么我们应该使用读已提交模式, 或者是在可重复读模式里在执行命令之前小心地获取锁。 在可重复读事务里获取的锁保证了不会有其它修改该表的事务正在运行, 但是如果事务看到的快照早于锁的获取,那么它可能早于这个表中现在已经提交的改变。 一个可重复读事务的快照实际上是在它的第一个查询或者数据修改命令(SELECT, INSERT, UPDATEDELETE)开始的时候冻结的, 因此我们可以在快照冻结之前明确获取锁。