两次查询放在同一个事务中,结果仍可能发生变化。遇到这种现象,不能只凭“已经开启事务”就认定数据库没有保持应有的行为。还需要看隔离级别,以及两次查询之间究竟发生了什么。

PostgreSQL 17文档的Read Committed章节明确说明,这一默认隔离级别下,不带FOR UPDATE或FOR SHARE子句的普通SELECT,只看到查询开始前已经提交的数据。查询执行期间其他事务新提交的变化,不会加入该条查询开始时的快照。

同一段也给出关键限定:两条连续SELECT即使处于同一事务,仍可能看到不同数据。条件是其他事务在第一条SELECT开始之后、第二条SELECT开始之前提交了变化。因此,一条查询的快照保持不变,与整个事务的每条查询都共用同一快照,并不是一回事。

可以用一个自拟的时间顺序理解:事务甲先读取一份项目列表,事务乙随后提交了列表中的变化,甲再执行第二次普通查询。若甲采用文中所述的Read Committed,两次结果可能不同。这只是依据文档构造的说明,没有连接实际数据库,也不是对任何线上故障的复现记录。

还应保留原文中的另一点:SELECT能够看到本事务此前更新所产生的效果,即使这些更新尚未提交。因而“只读已提交数据”作为一句简写,若省掉本事务自身变化这一条件,也会使解释不完整。这里讨论的是可见性,不是所有数据库操作都具有完全相同的处理方式。

整理并发问题时,较有用的材料是实际版本、隔离级别、查询类别及各事务的时间关系。若日志只说明两条查询不同,却没有另一事务何时提交的记录,就不足以由本文替它确定原因。也不能把普通SELECT的描述直接移到带锁查询、更新或合并语句上;文档为这些命令另作了说明。

这一知识用于理解预期行为,不提供生产隔离级别修改或性能优化方案。读到结果变化后,先确认它是否发生在文档限定的上下文,能让排查问题更具体;是否需要另一种一致性要求,仍要结合应用的真实需求与验证。

信息来源

本文基于上述公开资料整理,未使用来源页面的图片、视频或嵌入媒体。