在概念上,快照保存了表在一个时间点上的所有可见数据,保证后续访问快照中的数据不会受到其他事务的影响。

在事务层面,PostgreSQL 通过快照来描述不同事务能够看到的数据。

例如对于读已提交隔离级别的事务而言,会在事务中的每一个 SQL 操作开始前创建一个快照。这个 SQL 操作会在快照创建时看到的所有数据上执行,避免了其他事务的影响。 对于可重复读隔离级别的事务而言,会在事务开始时创建一个快照。这个事务中的所有 SQL 操作都会在同一个快照上执行。

正如快照的定义,快照需要持久化元组的状态。 PostgreSQL 已经通过 行版本化 在元组中保存了用于处理并发事务的元信息。 唯一的问题在于 CLOG。在判断 行版本化 元组的有效性时,PostgreSQL 需要访问 CLOG 来确认创建或删除元组事务的是否已经提交。 根据事务是否已经提交,元组对当前事务的可见性可能会发生变化。

因此快照需要额外保存所有事务的状态,是已提交还是已中止(等价于未提交),保证每次访问快照中的数据时都能得到一致的结果。

为了避免物理存储所有事务的状态,PostgreSQL 假定活跃(即未提交)的事务 id 相对于所有事务 id 非常少,并且集中在一个小范围内。 具体而言,PostgreSQL 会计算得到快照的 xminxmax,其中 xmin 是最老的活跃事务 id,xmax 是最新的已提交事务 id + 1。 所有 xid < xmin 的事务都已经提交或者已中止,所有 xid >= xmax 的事务在快照的时刻要么未提交,要么已中止,要么还未创建。 因此快照只需要额外存储 范围内所有的活跃或已中止的事务 id 即可。

在访问元组时,元组可见当且仅当下列条件同时成立

  1. tuple.xmin < snapshot.xmax && tuple.xmin not in snapshot.xip_list,表示创建元组的事务已经提交或已中止。
  2. tuple.xmin_committed == true || CLOG[tuple.xmin] == COMMITTED,表示创建元组的事务已经提交。
  3. tuple.xmax_aborted == true || tuple.xmax >= snapshot.xmax || tuple.xmax in snapshot.xip_list,表示删除元组的事务已经中止或在快照创建时仍未提交。

对于第二个条件,因为一个事务在完成后其状态便不会再发生变化,因此若条件一成立,事务在快照创建时已经完成,在快照创建后 CLOG 或者 tuple.xmin_committedtuple.xmin_aborted 反映的事务状态便是快照创建时的状态。