9.4. 应用层的数据完整性检查
因为不管哪种隔离级别,对 PostgreSQL 的读动作不会锁定数据, 一个事务读取的数据可能被另一个事务覆盖。换句话说,如果一条 SELECT 返 回了一行,这并不意味着在返回该行时该行还存在 (也就是说在语句完成或事务开始后的某时) 该行可能已经被一个在此事务开始之后提交的事务更新或者删除。 即使该行"现在"仍然有效,那它也可能在当前事务提交或者 回滚之前被改变或者删除。
另外一个认识它的方法是每个事务都看到一个数据库内容的快照,而并行 执行的事物很可能看到不同的快照.因此不管怎样,整个"现在"的概念都 是值得怀疑的.不过如果客户端应用相互隔离,那么这就不是个大问题, 但是如果客户端之间在数据库外部相互之间通过通道通讯,那就可能有严重的 歧义.
要保证一行的实际存在和避免其被并行更新,我们必须使用 SELECT FOR UPDATE 或者 或者合适的 LOCK TABLE 语句。 (SELECT FOR UPDATE 只是对并行更新 锁住返回的行,而 LOCK TABLE 保护整个表.) 当从其他环境向 PostgreSQL 里用可串行化模式移植应用时一定要把这些问题考虑进去。
注意: 在版本 6.5 前,PostgreSQL 使用读动作锁,因而当从以前的 PostgreSQL 版本向6.5(或更高版本)升级时也要考虑这些问题。
全局有效性检查需要额外考虑 MVCC 下的问题。 比如,一个银行应用可能会希望检查一个表重的所有扣款总和等于另外一个 表中的加款总和,同时两个表还会被活跃地更新。在读已提交模式下 比较两个连续的 SELECT SUM(...) 命令的结果是不可靠的, 因为第二个查询很可能会包含第一个没计算的事务提交的结果。在一个可串行化 的事务里进行两个求和则给出在可串行化事务开始之前提交的所有事务产生的 精确的结果 --- 但我们还是会合理地置疑在结果提交的时候,它们是否还相关。 如果可串行化事务本身在试图做一致性检查之前进行了某些变更,那么检查 的有用性就更加值得讨论了,因为现在它包含了一些,但不是全部,事务开始 后的变化。在这种情况下,一个仔细的人会希望锁住所有需要检查的表, 这样才能获得一个无可置疑的当前现状的图象。一个 SHARE 模式 (或者更高级)的锁保证在被锁定表中除了当前事务之外,没有未提交的更新。
还要注意如果我们依赖明确锁定来避免并发更新,那么我们应该使用读已提交模式, 或者是在可串行化模式里在执行查询之前小心地获取锁。在可串行化事务里的明确 锁定保证了不会有其它正在运行的修改该表的事务存在 --- 但是如果事务看到的 快照提前获取了锁,那么它可能提前把一些现在已经提交的改变放到表中。 一个可串行化事务的快照实际上是在它的第一个查询开始的时候冻结的(SELECT, INSERT,UPDATE,或 DELETE), 因此我们可能在快照冻结之前获取明确的锁。