postgres MVCC 数据库事务

快照是实现事务隔离的基础,快照规定了可见的数据范围,不同的快照之间相互隔离。每个事务都会持有一个快照,通过这个快照来确定可读的数据范围,以及事务中写的数据所属的版本号,从而实现事务之间的隔离。

那么在postgres当中快照在代码中是如何实现的呢?

快照类型

首先来看一下快照的类型,在postgres当中快照并不只是单纯用于MVCC,还有其它的用处。

typedef enum SnapshotType  
{  
    SNAPSHOT_MVCC = 0,  
  
    SNAPSHOT_SELF,  
  
    SNAPSHOT_ANY,  
  
    SNAPSHOT_TOAST,  
  
    SNAPSHOT_DIRTY,  
  
    SNAPSHOT_HISTORIC_MVCC,  
  
    SNAPSHOT_NON_VACUUMABLE  
} SnapshotType;

不同类型的快照用于不同的场景,限制了不同的可见范围。

快照类型SNAPSHOT_MVCCSNAPSHOT_SELFSNAPSHOT_ANYSNAPSHOT_TOASTSNAPSHOT_DIRTYSNAPSHOT_HISTORIC_MVCCSNAPSHOT_NON_VACUUMABLE
可见1. 快照开始时间已提交的事务
2. 本事务之前的命令
1. 已提交的事务
2. 事务前执行的命令
3. 当前命令做的修改
所有行都可见TOAST行可见和SNAPSHOT_SELF类似但是进行中的事务做得修改也可见遵循SNAPSHOT_MVCC的规则,但是支持在时间旅行上下文中调用可能对一些事务可见的记录
不可见1. 在快照当中显示正在进行的事务
2. 在本快照启动之后启动的事务
3. 当前命令(自动提交事务的sql,非本事务)所做的修改
1. 当前快照显示进行中的事务
隔离级别可重复读读已提交读未提交

快照结构

这些事务类型都是采用统一的快照结构体来表达的,虽然以后可能拆分为不同的类别使用不同的快照结构体,这样可以保证字段的含义都是独立的,不过嘛现在还是继续基于这个统一的结构体进行说明。

typedef struct SnapshotData  
{  
    SnapshotType snapshot_type; /* 快照类型 */  
  
    TransactionId xmin;          /* all XID < xmin are visible to me */  
    TransactionId xmax;          /* all XID >= xmax are invisible to me */  
 
	// 对于MVCC类型的快照来说,xip记录的是运行中的事务id数组
	// 对于historic MVCC快照来说,含义是颠倒的,表示已经提交的事务id数组
    TransactionId *xip;  
    uint32    xcnt;        /* xip当中事务的数据 */  
	// 对于非历史MVCC快照来说,包含正在运行的子事务id
	// 对于历史快照来说,包含要这个事务回放的所有的事务id,包括顶级事务id  
	// subxip当中的所有id都要>=xmin,但是并不保证小于xmax
    TransactionId *subxip;  
    int32     subxcnt;      /* subxip元素个数 */  
    bool      suboverflowed; /* subxip数组溢出了吗? */  
  
    bool      takenDuringRecovery;   /* recovery-shaped snapshot? */  
    bool      copied;          /* false if it's a static snapshot */  
	// 在此事务当中,cid小于curcid的命令才可见
    CommandId  curcid;          /* in my xact, CID < curcid are visible */  
	// HeapTupleSatisfiesDirty使用的额外字段,MVCC不需要关注
    uint32    speculativeToken;  
  
    // SNAPSHOT_NON_VACUUMABLE类型快照所用字段,用来决定一个行是否可以被清理掉
    struct GlobalVisState *vistest;  
  
    /*  
     * Book-keeping information, used by the snapshot manager     */
    // 快照的使用信息,用于快照管理
    // 在ActiveSnapshot栈中的引用计数
    uint32    active_count;  /* refcount on ActiveSnapshot stack */  
    // RegisteredSnapshots的引用计数
    uint32    regd_count;       /* refcount on RegisteredSnapshots */  
    // RegisteredSnapshots 堆中的连接
    pairingheap_node ph_node;  /* link in the RegisteredSnapshots heap */  
	// 快照记录的时间戳
    TimestampTz whenTaken;    /* timestamp when snapshot was taken */  
    // 快照记录时在WAL流中的位置
    XLogRecPtr lsn;         /* position in the WAL stream when taken */  
 
	// 使用GetSnapshotData()构建此快照时的事务完成计数。能够避免在上次GetSnapshotData()之后没有事务完成时重新计算静态快照。
	// 即调用GetSnapshotData()发现计数没有增加,则说明数据库状态没有改变,这个快照仍然是有效的,无需重新计算生成
	uint64    snapXactCompletionCount;  
} SnapshotData;