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

关于使用读已提交事务的数据完整性强制执行业务规则是很难的,由于数据视图从每个语句偏移,如果发生写入冲突,则单一语句可以不限制自身到语句快照。

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

正如Section 13.2.3提及到的,可串行化事务是可重复读事务,其中添加了 读/写冲突危险模式的非阻塞监测。当这个模式被监测时,有可能导致明显的执行顺序周期,其中所 涉及到的事务回滚以打破这个周期。

13.4.1. 可串行化事务执行一致性

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

当使用这种技术时,如果应用软件完成框架,其中回滚到 可串行化失败的地方进行自动重启事务,它可以避免为应用参数产生不必要的负担。 设置default_transaction_isolation可串行化是一个很好的主意。 明智的采取一些措施确保没有其他可用的事务隔离级别,或者是无意的或者破坏完整性检查, 通过触发器中的事务隔离级别检查。

参见Section 13.2.3获取性能建议。

Warning
这一级别的完整性保护使用序列化事务还没有延伸到热备模式(Section 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中你必须更新行,即使没有值需要被改变。 来自获取同一个锁或者执行UPDATE或者DELETESELECT FOR UPDATE 暂时阻塞 其他事务可能影响锁定行,但是一旦事务持有这个锁提交或者回滚,被阻塞事务将继续做冲突操作,除非 当锁被持有的时候,执行实际UPDATE行。

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

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