PostgreSQL 源码解读(263)- PG 14(Improving connection scalability)#15

PostgreSQL 14在MVCC上面有所增强,以保证在大批量客户端连接场景下性能不会出现显著下降(详细可参见参考资料中的链接),本节对函数GetSnapshotData进行跟踪分析。

一、数据结构

ProcArrayStruct


/* Our shared memory area */
typedef struct ProcArrayStruct
{
    //有效的procs个数
    int            numProcs;        /* number of valid procs entries */
    //分配的procs数组大小
    int            maxProcs;        /* allocated size of procs array */
    /*
     * Known assigned XIDs handling
     * 已知已分配的XIDs
     */
    int            maxKnownAssignedXids;    /* allocated size of array ,分配的数组大小*/
    int            numKnownAssignedXids;    /* current # of valid entries,当前第几个entry */
    int            tailKnownAssignedXids;    /* index of oldest valid element,最旧有效元素的索引编号,在数组的尾部 */
    int            headKnownAssignedXids;    /* index of newest element, + 1,最新元素编号+1,在数组的头部 */
    slock_t        known_assigned_xids_lck;    /* protects head/tail pointers,用于保护head/tail指针的锁 */
    /*
     * Highest subxid that has been removed from KnownAssignedXids array to
     * prevent overflow; or InvalidTransactionId if none.  We track this for
     * similar reasons to tracking overflowing cached subxids in PGPROC
     * entries.  Must hold exclusive ProcArrayLock to change this, and shared
     * lock to read it.
     * 从KnownAssignedXids中移除用于避免溢出的的subxid最大值,如无,则为InvalidTransactionId
     * 跟踪该值的理由与跟踪溢出PGPROC中的subxids类似。
     * 在修改该值前必须持有ProcArrayLock独占锁,读取则需要共享锁。
     */
    TransactionId lastOverflowedXid;
    /* oldest xmin of any replication slot */
    /* 复制槽中最小的xmin */
    TransactionId replication_slot_xmin;
    /* oldest catalog xmin of any replication slot */
    /* 复制槽中最小的字典xmin */
    TransactionId replication_slot_catalog_xmin;
    /* indexes into allProcs[], has PROCARRAY_MAXPROCS entries */
    /* allProcs索引,个数 = PROCARRAY_MAXPROCS */
    int            pgprocnos[FLEXIBLE_ARRAY_MEMBER];
} ProcArrayStruct;

PROC_HDR


/*
 * There is one ProcGlobal struct for the whole database cluster.
 * 全局的ProcGlobal(PROC_HDR)结构体
 *
 * Adding/Removing an entry into the procarray requires holding *both*
 * ProcArrayLock and XidGenLock in exclusive mode (in that order). Both are
 * needed because the dense arrays (see below) are accessed from
 * GetNewTransactionId() and GetSnapshotData(), and we don't want to add
 * further contention by both using the same lock. Adding/Removing a procarray
 * entry is much less frequent.
 * 对proc数组的插入和删除,需要以独占的模式持有ProcArrayLock和XidGenLock,
 * 原因是GetNewTransactionId()和GetSnapshotData()函数需要访问,并且我们不希望使用同样的锁来增加争用.
 * 其实,插入和删除proc数组并不频繁.
 *
 * Some fields in PGPROC are mirrored into more densely packed arrays (e.g.
 * xids), with one entry for each backend. These arrays only contain entries
 * for PGPROCs that have been added to the shared array with ProcArrayAdd()
 * (in contrast to PGPROC array which has unused PGPROCs interspersed).
 * PGPROC中的一些字段镜像到更稠密的数组中(如xids),每一个后台进程都有一项.
 * 这些数组只包含使用ProcArrayAdd()加入到共享数组的PGPROCs.
 *
 * The dense arrays are indexed by PGPROC->pgxactoff. Any concurrent
 * ProcArrayAdd() / ProcArrayRemove() can lead to pgxactoff of a procarray
 * member to change.  Therefore it is only safe to use PGPROC->pgxactoff to
 * access the dense array while holding either ProcArrayLock or XidGenLock.
 * 稠密数组通过PGPROC->pgxactoff字段进行索引,所有同步ProcArrayAdd和ProcArrayRemove操作
 * 会改变procarray中的pgxactoff成员变量.
 *
 * As long as a PGPROC is in the procarray, the mirrored values need to be
 * maintained in both places in a coherent manner.
 * 由于PGPROC在proc数组中,镜像值需要以一致的方式进行运维.
 *
 * The denser separate arrays are beneficial for three main reasons: First, to
 * allow for as tight loops accessing the data as possible. Second, to prevent
 * updates of frequently changing data (e.g. xmin) from invalidating
 * cachelines also containing less frequently changing data (e.g. xid,
 * statusFlags). Third to condense frequently accessed data into as few
 * cachelines as possible.
 * 基于以下3点原因,更稠密单独的数组会带来不少好处.
 * 1.紧密的循环访问数据成为可能;
 * 2.避免了对修改频度不高的数据进行修改导致CPU缓存无效的情况出现;
 * 3.使频繁访问的数据放在小量的CPU缓存中成为可能.
 *
 * There are two main reasons to have the data mirrored between these dense
 * arrays and PGPROC. First, as explained above, a PGPROC's array entries can
 * only be accessed with either ProcArrayLock or XidGenLock held, whereas the
 * PGPROC entries do not require that (obviously there may still be locking
 * requirements around the individual field, separate from the concerns
 * here). That is particularly important for a backend to efficiently checks
 * it own values, which it often can safely do without locking.  Second, the
 * PGPROC fields allow to avoid unnecessary accesses and modification to the
 * dense arrays. A backend's own PGPROC is more likely to be in a local cache,
 * whereas the cachelines for the dense array will be modified by other
 * backends (often removing it from the cache for other cores/sockets). At
 * commit/abort time a check of the PGPROC value can avoid accessing/dirtying
 * the corresponding array value.
 * 让这些稠密数组和PGPROC之间生成数据镜像,有两个主要原因:
 * 1.如上所述,PGPROC数组条目需要在持有ProcArrayLock和XidGenLock的前提下才能访问,但实际上并不需要.
 *   在没有锁的情况下,后台进程有效且安全的检查自己的值是十分重要的.
 * 2.PGPROC中的字段避免不必要的访问和修改.后台进程自己的PGPROC更应该在自己的本地缓存中,
 *   而无需理会数组会被其他进程修改.在事务提交或回滚时,检查PGPROC值可以避免访问或者弄脏相应的数组值
 *
 * Basically it makes sense to access the PGPROC variable when checking a
 * single backend's data, especially when already looking at the PGPROC for
 * other reasons already.  It makes sense to look at the "dense" arrays if we
 * need to look at many / most entries, because we then benefit from the
 * reduced indirection and better cross-process cache-ability.
 * 检查单个后台进程数据时,访问PGPROC值是比较敏感的,特别是在已经因为其他原因检索过PGPROC的情况下
 *
 * When entering a PGPROC for 2PC transactions with ProcArrayAdd(), the data
 * in the dense arrays is initialized from the PGPROC while it already holds
 * ProcArrayLock.
 * 在2PC事务的情况下,通过ProcArrayAdd方法进入PGPROC时,
 *   数组中的数据在持有ProcArrayLock锁的情况下初始化数组.
 */
typedef struct PROC_HDR
{
    /* Array of PGPROC structures (not including dummies for prepared txns) */
    //PGPROC结构体数组
    PGPROC       *allProcs;
    /* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
    // 镜像每一个PGPROC.xid
    TransactionId *xids;
    /*
     * Array mirroring PGPROC.subxidStatus for each PGPROC currently in the
     * procarray.
     */
    //镜像PGPROC.subxidStatus
    XidCacheStatus *subxidStates;
    /*
     * Array mirroring PGPROC.statusFlags for each PGPROC currently in the
     * procarray.
     */
    //镜像PGPROC.statusFlags
    uint8       *statusFlags;
    /* Length of allProcs array */
    //allProcs数组的大小
    uint32        allProcCount;
    /* Head of list of free PGPROC structures */
    //空闲PGPROC结构体链表头
    PGPROC       *freeProcs;
    /* Head of list of autovacuum's free PGPROC structures */
    //autovacuum进程空闲PGPROC结构体链表头
    PGPROC       *autovacFreeProcs;
    /* Head of list of bgworker free PGPROC structures */
    //bgworker进程
    PGPROC       *bgworkerFreeProcs;
    /* Head of list of walsender free PGPROC structures */
    //walsender
    PGPROC       *walsenderFreeProcs;
    /* First pgproc waiting for group XID clear */
    //等待XID组清理的第一个pgproc
    pg_atomic_uint32 procArrayGroupFirst;
    /* First pgproc waiting for group transaction status update */
    //等待事务状态组更新的首个pgproc
    pg_atomic_uint32 clogGroupFirst;
    /* WALWriter process's latch */
    //WALWriter进程latch
    Latch       *walwriterLatch;
    /* Checkpointer process's latch */
    //检查点进程latch
    Latch       *checkpointerLatch;
    /* Current shared estimate of appropriate spins_per_delay value */
    //spins_per_delay
    int            spins_per_delay;
    /* The proc of the Startup process, since not in ProcArray */
    //Startup进程的proc结构体,不再Proc数组中
    PGPROC       *startupProc;
    int            startupProcPid;
    /* Buffer id of the buffer that Startup process waits for pin on, or -1 */
    //Startup进程等待pin的缓存ID
    int            startupBufferPinWaitBufId;
} PROC_HDR;
extern PGDLLIMPORT PROC_HDR *ProcGlobal;

PGPROC

/*
 * Note: MAX_BACKENDS is limited to 2^18-1 because that's the width reserved
 * for buffer references in buf_internals.h.  This limitation could be lifted
 * by using a 64bit state; but it's unlikely to be worthwhile as 2^18-1
 * backends exceed currently realistic configurations. Even if that limitation
 * were removed, we still could not a) exceed 2^23-1 because inval.c stores
 * the backend ID as a 3-byte signed integer, b) INT_MAX/4 because some places
 * compute 4*MaxBackends without any overflow check.  This is rechecked in the
 * relevant GUC check hooks and in RegisterBackgroundWorker().
 * 注意:MAX_BACKENDS限制为2^18-1,
 *   这是因为该值为buf_internals.h中定义的缓存依赖的最大宽度.
 * 该限制可以通过使用64bit的状态来提升,但它看起来并不值当.
 * 如果去掉该限制,我们仍然不能够超过:
 *   a) 2^23-1,因为inval.c使用3个字节的有符号整数存储后台进程ID
 *   b) INT_MAX/4 ,因为某些地方没有任何的溢出检查,直接计算4*MaxBackends的值.
 * 该值会在相关的GUC检查钩子和RegisterBackgroundWorker()函数中检查.
 */
#define MAX_BACKENDS    0x3FFFF
/* shmqueue.c */
typedef struct SHM_QUEUE
{
    struct SHM_QUEUE *prev;
    struct SHM_QUEUE *next;
} SHM_QUEUE;
/*
 * An invalid pgprocno.  Must be larger than the maximum number of PGPROC
 * structures we could possibly have.  See comments for MAX_BACKENDS.
 * 无效的pg进程号.
 * 必须大于我们可能拥有的最大的PGPROC数目.
 * 详细解释见MAX_BACKENDS
 */
#define INVALID_PGPROCNO        PG_INT32_MAX
/*
 * Each backend has a PGPROC struct in shared memory.  There is also a list of
 * currently-unused PGPROC structs that will be reallocated to new backends.
 * 每个后台进程在共享内存中都有一个PGPROC结构体.
 * 存在未使用的PGPROC结构体链表,用于为新的后台进程重新进行分配.
 * 
 * links: list link for any list the PGPROC is in.  When waiting for a lock,
 * the PGPROC is linked into that lock's waitProcs queue.  A recycled PGPROC
 * is linked into ProcGlobal's freeProcs list.
 * links: PGPROC所在的链表的链接.
 *   在等待锁时,PGPROC链接到该锁的waiProc队列中.
 * 回收的PGPROC链接到ProcGlobal的freeProcs链表中.
 *
 * Note: twophase.c also sets up a dummy PGPROC struct for each currently
 * prepared transaction.  These PGPROCs appear in the ProcArray data structure
 * so that the prepared transactions appear to be still running and are
 * correctly shown as holding locks.  A prepared transaction PGPROC can be
 * distinguished from a real one at need by the fact that it has pid == 0.
 * The semaphore and lock-activity fields in a prepared-xact PGPROC are unused,
 * but its myProcLocks[] lists are valid.
 * 注意:twophase.c也会为每一个当前已准备妥当的事务配置一个虚拟的PGPROC结构.
 * 这些PGPROCs在数组ProcArray数据结构中出现,以便已准备的事务看起来仍在运行,
 *   并正确的显示为持有锁.
 * 已准备妥当的事务PGPROC与一个真正的PGPROC事实上的区别是pid == 0.
 * 在prepared-xact PGPROC中的信号量和活动锁域字段没有使用,但myProcLocks[]链表是有效的.
 */
struct PGPROC
{
    /* proc->links MUST BE FIRST IN STRUCT (see ProcSleep,ProcWakeup,etc) */
    //proc->links必须是结构体的第一个域(参考ProcSleep,ProcWakeup...等)
    //如进程在链表中,这是链表的链接
    SHM_QUEUE   links;          /* list link if process is in a list */
    //持有该PGPROC的procglobal链表数组
    PGPROC    **procgloballist; /* procglobal list that owns this PGPROC */
    //可以休眠的信号量
    PGSemaphore sem;            /* ONE semaphore to sleep on */
    //状态为:STATUS_WAITING, STATUS_OK or STATUS_ERROR
    int         waitStatus;     /* STATUS_WAITING, STATUS_OK or STATUS_ERROR */
    //进程通用的latch
    Latch       procLatch;      /* generic latch for process */
    //运行中的进程正在执行的最高层的事务本地ID,如无运行则为InvalidLocalTransactionId
    LocalTransactionId lxid;    /* local id of top-level transaction currently
                                 * being executed by this proc, if running;
                                 * else InvalidLocalTransactionId */
    //后台进程的ID,如为虚拟事务则为0
    int         pid;            /* Backend's process ID; 0 if prepared xact */
    int         pgprocno;
    /* These fields are zero while a backend is still starting up: */
    //------------ 这些域在进程正在启动时为0
    //已分配的后台进程的backend ID
    BackendId   backendId;      /* This backend's backend ID (if assigned) */
    //该进程使用的数据库ID
    Oid         databaseId;     /* OID of database this backend is using */
    //使用该进程的角色ID
    Oid         roleId;         /* OID of role using this backend */
    //该进程使用的临时schema OID
    Oid         tempNamespaceId;    /* OID of temp schema this backend is
                                     * using */
    //如后台进程,则为T
    bool        isBackgroundWorker; /* true if background worker. */
    /*
     * While in hot standby mode, shows that a conflict signal has been sent
     * for the current transaction. Set/cleared while holding ProcArrayLock,
     * though not required. Accessed without lock, if needed.
     * 如在hot standby模式,显示已为当前事务发送冲突信号.
     * 尽管不需要,设置/清除持有的ProcArrayLock.
     * 如需要,则在没有持有锁的情况下访问.
     */
    bool        recoveryConflictPending;
    /* Info about LWLock the process is currently waiting for, if any. */
    //-------------- 进程正在等待的LWLock相关信息
    //等待LW lock,为T
    bool        lwWaiting;      /* true if waiting for an LW lock */
    //正在等的LWLock锁模式
    uint8       lwWaitMode;     /* lwlock mode being waited for */
    //等待链表中的位置
    proclist_node lwWaitLink;   /* position in LW lock wait list */
    /* Support for condition variables. */
    //-------------- 支持条件变量
    //CV等待链表中的位置
    proclist_node cvWaitLink;   /* position in CV wait list */
    /* Info about lock the process is currently waiting for, if any. */
    //-------------- 进程正在等待的锁信息
    /* waitLock and waitProcLock are NULL if not currently waiting. */
    //如没有在等待,则waitLock和waitProcLock为NULL
    //休眠...等待的锁对象
    LOCK       *waitLock;       /* Lock object we're sleeping on ... */
    //等待锁的每个持锁人信息
    PROCLOCK   *waitProcLock;   /* Per-holder info for awaited lock */
    //等待的所类型
    LOCKMODE    waitLockMode;   /* type of lock we're waiting for */
    //该进程已持有锁的类型位掩码
    LOCKMASK    heldLocks;      /* bitmask for lock types already held on this
                                 * lock object by this backend */
    /*
     * Info to allow us to wait for synchronous replication, if needed.
     * waitLSN is InvalidXLogRecPtr if not waiting; set only by user backend.
     * syncRepState must not be touched except by owning process or WALSender.
     * syncRepLinks used only while holding SyncRepLock.
     * 允许我们等待同步复制的相关信息.
     * 如无需等待,则waitLSN为InvalidXLogRecPtr;仅允许由用户后台设置。
     * 除非拥有process或WALSender,否则不能修改syncRepState。
     * 仅在持有SyncRepLock时使用的syncrepink。
     */
    //--------------------- 
    //等待该LSN或者更高的LSN
    XLogRecPtr  waitLSN;        /* waiting for this LSN or higher */
    //同步复制的等待状态
    int         syncRepState;   /* wait state for sync rep */
    //如进程处于syncrep队列中,则该值保存链表链接
    SHM_QUEUE   syncRepLinks;   /* list link if process is in syncrep queue */
    /*
     * All PROCLOCK objects for locks held or awaited by this backend are
     * linked into one of these lists, according to the partition number of
     * their lock.
     * 该后台进程持有或等待的锁相关的所有PROCLOCK对象链接在这些链表的末尾,
     *   根据棣属于这些锁的分区号进行区分.
     */
    SHM_QUEUE   myProcLocks[NUM_LOCK_PARTITIONS];
    //子事务的XIDs
    struct XidCache subxids;    /* cache for subtransaction XIDs */
    /* Support for group XID clearing. */
    /* true, if member of ProcArray group waiting for XID clear */
    //支持XID分组清除
    //如属于等待XID清理的ProcArray组,则为T
    bool        procArrayGroupMember;
    /* next ProcArray group member waiting for XID clear */
    //等待XID清理的下一个ProcArray组编号
    pg_atomic_uint32 procArrayGroupNext;
    /*
     * latest transaction id among the transaction's main XID and
     * subtransactions
     * 在事务主XID和子事务之间的最后的事务ID
     */
    TransactionId procArrayGroupMemberXid;
    //进程的等待信息
    uint32      wait_event_info;    /* proc's wait information */
    /* Support for group transaction status update. */
    //--------------- 支持组事务状态更新
    //clog组成员,则为T
    bool        clogGroupMember;    /* true, if member of clog group */
    //下一个clog组成员
    pg_atomic_uint32 clogGroupNext; /* next clog group member */
    //clog组成员事务ID
    TransactionId clogGroupMemberXid;   /* transaction id of clog group member */
    //clog组成员的事务状态
    XidStatus   clogGroupMemberXidStatus;   /* transaction status of clog
                                             * group member */
    //属于clog组成员的事务ID的clog page
    int         clogGroupMemberPage;    /* clog page corresponding to
                                         * transaction id of clog group member */
    //clog组成员已提交记录的WAL位置
    XLogRecPtr  clogGroupMemberLsn; /* WAL location of commit record for clog
                                     * group member */
    /* Per-backend LWLock.  Protects fields below (but not group fields). */
    //每一个后台进程一个LWLock.保护下面的域字段(非组字段)
    LWLock      backendLock;
    /* Lock manager data, recording fast-path locks taken by this backend. */
    //---------- 锁管理数据,记录该后台进程以最快路径获得的锁
    //每一个fast-path slot的锁模式
    uint64      fpLockBits;     /* lock modes held for each fast-path slot */
    //rel oids的slots
    Oid         fpRelId[FP_LOCK_SLOTS_PER_BACKEND]; /* slots for rel oids */
    //是否持有fast-path VXID锁
    bool        fpVXIDLock;     /* are we holding a fast-path VXID lock? */
    //fast-path VXID锁的lxid
    LocalTransactionId fpLocalTransactionId;    /* lxid for fast-path VXID
                                                 * lock */
    /*
     * Support for lock groups.  Use LockHashPartitionLockByProc on the group
     * leader to get the LWLock protecting these fields.
     */
    //--------- 支持锁组.
    //          在组leader中使用LockHashPartitionLockByProc获取LWLock保护这些域
    //锁组的leader,如果"我"是其中一员
    PGPROC     *lockGroupLeader;    /* lock group leader, if I'm a member */
    //如果"我"是leader,这是成员的链表
    dlist_head  lockGroupMembers;   /* list of members, if I'm a leader */
    //成员连接,如果"我"是其中一员
    dlist_node  lockGroupLink;  /* my member link, if I'm a member */
};
/* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
//注意:"typedef struct PGPROC PGPROC"在storage/lock.h中出现
extern PGDLLIMPORT PGPROC *MyProc;
extern PGDLLIMPORT struct PGXACT *MyPgXact;

SnapshotData


/*
 * The different snapshot types.  We use SnapshotData structures to represent
 * both "regular" (MVCC) snapshots and "special" snapshots that have non-MVCC
 * semantics.  The specific semantics of a snapshot are encoded by its type.
 * 快照类型。
 * 使用SnapshotData结构体表示常规的(MVCC)的快照和特别的快照(非MVCC语义)。
 * 快照的特殊语义通过其类型进行编码。
 *
 * The behaviour of each type of snapshot should be documented alongside its
 * enum value, best in terms that are not specific to an individual table AM.
 * 每种快照类型的行为与其enum值一起记录而不是特定于单个表的AM。
 *
 * The reason the snapshot type rather than a callback as it used to be is
 * that that allows to use the same snapshot for different table AMs without
 * having one callback per AM.
 * 使用快照类型而不是回调函数的原因是对于不同的表AMs(访问方法)可使用相同的快照而不是每个AM使用一个回调函数
 */
typedef enum SnapshotType
{
    /*-------------------------------------------------------------------------
     * A tuple is visible iff the tuple is valid for the given MVCC snapshot.
     *
     * Here, we consider the effects of:
     * - all transactions committed as of the time of the given snapshot
     * - previous commands of this transaction
     *
     * Does _not_ include:
     * - transactions shown as in-progress by the snapshot
     * - transactions started after the snapshot was taken
     * - changes made by the current command
     * 元组对于给定的MVCC快照是有效的,则该元组可见
     * 考虑:
     *   给定快照时所有提交的事务
     *   该事务先前执行的命令
     * 不考虑:
     *   正在处理中的事务
     *   快照点之后的事务
     *   当前命令做出的修改
     * -------------------------------------------------------------------------
     */
    SNAPSHOT_MVCC = 0,//常规的MVCC
    /*-------------------------------------------------------------------------
     * A tuple is visible iff the tuple is valid "for itself".
     * 对于“自身”是有效,则元组可见
     *
     * Here, we consider the effects of:
     * - all committed transactions (as of the current instant)
     * - previous commands of this transaction
     * - changes made by the current command
     *
     * Does _not_ include:
     * - in-progress transactions (as of the current instant)
     * 考虑:
     *   所有已提交的事务(当前实例)
     *   本事务先前的命令
     *   当前命令的修改
     * 不考虑
     *   正在进行中的事务
     * -------------------------------------------------------------------------
     */
    SNAPSHOT_SELF,
    /*
     * Any tuple is visible.
     * 所有元组都是可见的
     */
    SNAPSHOT_ANY,
    /*
     * A tuple is visible iff the tuple is valid as a TOAST row.
     * 元组作为TOAST行为有效时,元组是可见的。
     */
    SNAPSHOT_TOAST,
    /*-------------------------------------------------------------------------
     * A tuple is visible iff the tuple is valid including effects of open
     * transactions.
     * 只要元组有效(包括开放事务的影响)则元组可见
     *
     * Here, we consider the effects of:
     * - all committed and in-progress transactions (as of the current instant)
     * - previous commands of this transaction
     * - changes made by the current command
     * 这里,考虑:
     *   所有已提交和处理中的事务
     *   该事务先前的命令
     *   当前命令作出的修改
     *
     * This is essentially like SNAPSHOT_SELF as far as effects of the current
     * transaction and committed/aborted xacts are concerned.  However, it
     * also includes the effects of other xacts still in progress.
     * 就当前和提交/中止事务的影响来看,本质上类似于SNAPSHOT_SELF。
     *
     * A special hack is that when a snapshot of this type is used to
     * determine tuple visibility, the passed-in snapshot struct is used as an
     * output argument to return the xids of concurrent xacts that affected
     * the tuple.  snapshot->xmin is set to the tuple's xmin if that is
     * another transaction that's still in progress; or to
     * InvalidTransactionId if the tuple's xmin is committed good, committed
     * dead, or my own xact.  Similarly for snapshot->xmax and the tuple's
     * xmax.  If the tuple was inserted speculatively, meaning that the
     * inserter might still back down on the insertion without aborting the
     * whole transaction, the associated token is also returned in
     * snapshot->speculativeToken.  See also InitDirtySnapshot().
     * 这种快照用于确定元组的可见性,传入的快照结构体用作输出参数返回当前影响元组的事务xids。
     * snapshot->xmin设置为元组的xmin,如果这是另外一个进行中的事务;
     *               设置为InvalidTransactionId如果元组的xmin对应的事务已完成提交/终止/或者是本事务
     * snapshot->xmax与元组xmax与此类似。
     * 如果tuple是speculatively插入的,这意味着插入事务仍可以继续插入,而不会中止整个事务,
     * 相关的令牌会通过snapshot—>speculativeToken返回。
     * -------------------------------------------------------------------------
     */
    SNAPSHOT_DIRTY,
    /*
     * A tuple is visible iff it follows the rules of SNAPSHOT_MVCC, but
     * supports being called in timetravel context (for decoding catalog
     * contents in the context of logical decoding).
     * 如遵循SNAPSHOT_MVCC规则,则元组是可见的,但支持在timetravel上下文中调用
     * (在逻辑解码上下文时解码catalog内容)
     */
    SNAPSHOT_HISTORIC_MVCC,
    /*
     * A tuple is visible iff the tuple might be visible to some transaction;
     * false if it's surely dead to everyone, i.e., vacuumable.
     *
     * For visibility checks snapshot->min must have been set up with the xmin
     * horizon to use.
     * 对于某些事务是可见的,则元组可见。
     * 如已确认“死亡”则为False,如需要被清理。
     * 对于可见性检查而言,snapshot->min必须使用xmin来配置
     */
    SNAPSHOT_NON_VACUUMABLE
} SnapshotType;
typedef struct SnapshotData *Snapshot;
#define InvalidSnapshot        ((Snapshot) NULL)
/*
 * Struct representing all kind of possible snapshots.
 * 对应所有可能快照的结构体
 * 
 * There are several different kinds of snapshots:
 * * Normal MVCC snapshots
 * * MVCC snapshots taken during recovery (in Hot-Standby mode)
 * * Historic MVCC snapshots used during logical decoding
 * * snapshots passed to HeapTupleSatisfiesDirty()
 * * snapshots passed to HeapTupleSatisfiesNonVacuumable()
 * * snapshots used for SatisfiesAny, Toast, Self where no members are
 *     accessed.
 * 快照类型包括(详见SnapshotType):
 *   普通的MVCC快照
 *   恢复期间的MVCC快照
 *   逻辑解码时的历史MVCC快照
 *   传递给HeapTupleSatisfiesDirty的快照
 *   传递给HeapTupleSatisfiesNonVacuumable的快照
 *   传递给SatisfiesAny,Toast,Self的快照
 *
 * TODO: It's probably a good idea to split this struct using a NodeTag
 * similar to how parser and executor nodes are handled, with one type for
 * each different kind of snapshot to avoid overloading the meaning of
 * individual fields.
 * TODO:通过NodeTag来拆分结构体
 */
typedef struct SnapshotData
{
    //快照类型
    SnapshotType snapshot_type; /* type of snapshot */
    /*
     * The remaining fields are used only for MVCC snapshots, and are normally
     * just zeroes in special snapshots.  (But xmin and xmax are used
     * specially by HeapTupleSatisfiesDirty, and xmin is used specially by
     * HeapTupleSatisfiesNonVacuumable.)
     * 余下字段用于MVCC快照,其他特殊的快照设置为0.
     * (xmin和xmax用于HeapTupleSatisfiesDirty,xmin用于HeapTupleSatisfiesNonVacuumable)
     *
     * An MVCC snapshot can never see the effects of XIDs >= xmax. It can see
     * the effects of all older XIDs except those listed in the snapshot. xmin
     * is stored as an optimization to avoid needing to search the XID arrays
     * for most tuples.
     * MVCC快照用于都看不到XIDs >= xmax的影响,但可以看到除了快照列表中的事务影响。
     * xmin作为优化的手段用于避免检索XID数组
     */
    TransactionId xmin;            /* all XID < xmin are visible to me */
    TransactionId xmax;            /* all XID >= xmax are invisible to me */
    /*
     * For normal MVCC snapshot this contains the all xact IDs that are in
     * progress, unless the snapshot was taken during recovery in which case
     * it's empty. For historic MVCC snapshots, the meaning is inverted, i.e.
     * it contains *committed* transactions between xmin and xmax.
     * xip用于存放所有正在进行中的事务,除了在恢复期间的快照。
     * 对于历史MVCC快照,意思的相反的,也就是说xip是xmin和xmax之间已提交的事务
     *
     * note: all ids in xip[] satisfy xmin <= xip[i] < xmax
     * xip数组中的元素满足:xmin <= xip[i] < xmax
     */
    TransactionId *xip;
    uint32        xcnt;            /* 数组大小,# of xact ids in xip[] */
    /*
     * For non-historic MVCC snapshots, this contains subxact IDs that are in
     * progress (and other transactions that are in progress if taken during
     * recovery). For historic snapshot it contains *all* xids assigned to the
     * replayed transaction, including the toplevel xid.
     * 对于非历史MVCC快照,包含正在进行中的子事务IDs。
     * 对于历史MVCC快照,包含所有分配给已回放事务的所有xids(包括顶层xid)
     *
     * note: all ids in subxip[] are >= xmin, but we don't bother filtering
     * out any that are >= xmax
     */
    TransactionId *subxip;
    int32        subxcnt;        /* 数组大小,# of xact ids in subxip[] */
    bool        suboverflowed;    /* 数组溢出?has the subxip array overflowed? */
    bool        takenDuringRecovery;    /* recovery-shaped snapshot? */
    bool        copied;            /* false if it's a static snapshot */
    CommandId    curcid;            /* 在本事务中,CID < curcid则可见,in my xact, CID < curcid are visible */
    /*
     * An extra return value for HeapTupleSatisfiesDirty, not used in MVCC
     * snapshots.
     * HeapTupleSatisfiesDirty额外的返回值
     */
    uint32        speculativeToken;
    /*
     * For SNAPSHOT_NON_VACUUMABLE (and hopefully more in the future) this is
     * used to determine whether row could be vacuumed.
     * 对于SNAPSHOT_NON_VACUUMABLE,用于确定哪些行可以被清理
     */
    struct GlobalVisState *vistest;
    /*
     * Book-keeping information, used by the snapshot manager
     * 用于快照管理器的信息
     */
    uint32        active_count;    /* refcount on ActiveSnapshot stack */
    uint32        regd_count;        /* refcount on RegisteredSnapshots */
    pairingheap_node ph_node;    /* link in the RegisteredSnapshots heap */
    TimestampTz whenTaken;        /* timestamp when snapshot was taken */
    XLogRecPtr    lsn;            /* position in the WAL stream when taken */
    /*
     * The transaction completion count at the time GetSnapshotData() built
     * this snapshot. Allows to avoid re-computing static snapshots when no
     * transactions completed since the last GetSnapshotData().
     */
    uint64        snapXactCompletionCount;
} SnapshotData;

VariableCacheData

/*
 * VariableCache is a data structure in shared memory that is used to track
 * OID and XID assignment state.  For largely historical reasons, there is
 * just one struct with different fields that are protected by different
 * LWLocks.
 * VariableCache是共享内存中的一种数据结构,用于跟踪OID和XID分配状态。
 * 由于历史原因,这个结构体有不同的字段,由不同的LWLocks保护。
 *
 * Note: xidWrapLimit and oldestXidDB are not "active" values, but are
 * used just to generate useful messages when xidWarnLimit or xidStopLimit
 * are exceeded.
 * 注意:xidWrapLimit和oldestXidDB是不"活跃"的值,在xidWarnLimit或xidStopLimit
 *   超出限制时用于产生有用的信息.
 */
typedef struct VariableCacheData
{
    /*
     * These fields are protected by OidGenLock.
     * 这些域字段通过OidGenLock字段保护
     */
    //下一个待分配的OID
    Oid         nextOid;        /* next OID to assign */
    //在必须执行XLOG work前可用OIDs
    uint32      oidCount;       /* OIDs available before must do XLOG work */
    /*
     * These fields are protected by XidGenLock.
     * 这些字段通过XidGenLock锁保护.
     */
    //下一个待分配的事务ID
    TransactionId nextXid;      /* next XID to assign */
    //集群范围内最小datfrozenxid
    TransactionId oldestXid;    /* cluster-wide minimum datfrozenxid */
    //在该XID开始强制执行autovacuum
    TransactionId xidVacLimit;  /* start forcing autovacuums here */
    //在该XID开始提出警告
    TransactionId xidWarnLimit; /* start complaining here */
    //在该XID开外,拒绝生成下一个XID
    TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
    //"世界末日"XID,需回卷
    TransactionId xidWrapLimit; /* where the world ends */
    //持有最小datfrozenxid的DB
    Oid         oldestXidDB;    /* database with minimum datfrozenxid */
    /*
     * These fields are protected by CommitTsLock
     * 这些字段通过CommitTsLock锁保护
     */
    TransactionId oldestCommitTsXid;
    TransactionId newestCommitTsXid;
    /*
     * These fields are protected by ProcArrayLock.
     * 这些字段通过ProcArrayLock锁保护
     */
    TransactionId latestCompletedXid;   /* newest XID that has committed or
                                         * aborted */
    /*
     * These fields are protected by CLogTruncationLock
     * 这些字段通过CLogTruncationLock锁保护
     */
    //clog中最古老的XID
    TransactionId oldestClogXid;    /* oldest it's safe to look up in clog */
} VariableCacheData;
//结构体指针
typedef VariableCacheData *VariableCache;
/* pointer to "variable cache" in shared memory (set up by shmem.c) */
//共享内存中的指针(通过shmem.c设置)
VariableCache ShmemVariableCache = NULL;

GlobalVisState


/*
 * State for the GlobalVisTest* family of functions. Those functions can
 * e.g. be used to decide if a deleted row can be removed without violating
 * MVCC semantics: If the deleted row's xmax is not considered to be running
 * by anyone, the row can be removed.
 * 用于配合GlobalVisTest*函数族使用的数据结构。
 * 这些函数用于确定删除的行在不违反MVCC语义的情况下是否可以被清除:
 * 如果被删除的行的xmax与所有运行的事务无关,那么可以清除。
 * 
 * To avoid slowing down GetSnapshotData(), we don't calculate a precise
 * cutoff XID while building a snapshot (looking at the frequently changing
 * xmins scales badly). Instead we compute two boundaries while building the
 * snapshot:
 *
 * 1) definitely_needed, indicating that rows deleted by XIDs >=
 *    definitely_needed are definitely still visible.
 *
 * 2) maybe_needed, indicating that rows deleted by XIDs < maybe_needed can
 *    definitely be removed
 *  
 *  为了避免影响GetSnapshotData()的性能,在构建快照时我们不会计算精确的cutoff XID,
 *  相反,我们会在构建快照时计算两个边界,分别是:
 *  1)definitely_needed,表示如果删除事务的XIDs >= definitely_needed,则可见
 *  2)maybe_needed,表示被XIDs < maybe_needed 事务删除的,可以确定被清除
 *
 * When testing an XID that falls in between the two (i.e. XID >= maybe_needed
 * && XID < definitely_needed), the boundaries can be recomputed (using
 * ComputeXidHorizons()) to get a more accurate answer. This is cheaper than
 * maintaining an accurate value all the time.
 * 对于XID >= maybe_needed && XID < definitely_needed的XID,
 * 边界需要使用函数ComputeXidHorizons重新计算来获取更精确的结果,这比在所有情况下都维护一个精确值成本要低
 *
 * As it is not cheap to compute accurate boundaries, we limit the number of
 * times that happens in short succession. See GlobalVisTestShouldUpdate().
 * 计算精确的边界成本高昂,因此需要限制短时间内出现的次数,详细参见GlobalVisTestShouldUpdate函数
 *
 * There are three backend lifetime instances of this struct, optimized for
 * different types of relations. As e.g. a normal user defined table in one
 * database is inaccessible to backends connected to another database, a test
 * specific to a relation can be more aggressive than a test for a shared
 * relation.  Currently we track four different states:
 * 对于不同的关系,出于优化的目的,该结构有三种不同的backend生命周期。
 * 比如某个数据库中用户定义的普通表对于连接到另外一个数据库的backends是不可见的,
 * 对某个特定关系的验证比起共享关系的验证要更为积极。我们跟踪四种不同的状态:
 *
 * 1) GlobalVisSharedRels, which only considers an XID's
 *    effects visible-to-everyone if neither snapshots in any database, nor a
 *    replication slot's xmin, nor a replication slot's catalog_xmin might
 *    still consider XID as running.
 * 1)GlobalVisSharedRels,事务对该Relation的操作会影响所有数据库中的快照、复制槽的xmin和复制槽的catalog_xmin。
 *
 * 2) GlobalVisCatalogRels, which only considers an XID's
 *    effects visible-to-everyone if neither snapshots in the current
 *    database, nor a replication slot's xmin, nor a replication slot's
 *    catalog_xmin might still consider XID as running.
 *
 *    I.e. the difference to GlobalVisSharedRels is that
 *    snapshot in other databases are ignored.
 * 2)GlobalVisCatalogRels,事务对该Relation的操作会影响当前数据库中的快照、复制槽的xmin和复制槽的catalog_xmin
 *    与GlobalVisSharedRels不同之处是其他数据库的快照会被忽略
 *
 * 3) GlobalVisDataRels, which only considers an XID's
 *    effects visible-to-everyone if neither snapshots in the current
 *    database, nor a replication slot's xmin consider XID as running.
 *
 *    I.e. the difference to GlobalVisCatalogRels is that
 *    replication slot's catalog_xmin is not taken into account.
 *
 * 3)GlobalVisDataRels,事务对该Relation的操作会影响当前数据库中的快照、复制槽的xmin
 *    与GlobalVisCatalogRels不同的是catalog_xmin不作考虑
 *
 * 4) GlobalVisTempRels, which only considers the current session, as temp
 *    tables are not visible to other sessions.
 *
 * 4)GlobalVisTempRels,只考虑当前会话,临时表对其他session不可见
 *
 * GlobalVisTestFor(relation) returns the appropriate state
 * for the relation.
 * GlobalVisTestFor(relation)返回关系正确的状态
 *
 * The boundaries are FullTransactionIds instead of TransactionIds to avoid
 * wraparound dangers. There e.g. would otherwise exist no procarray state to
 * prevent maybe_needed to become old enough after the GetSnapshotData()
 * call.
 * 边界使用FullTransactionIds而不是TransactionIds以避免因为回卷而导致的错误。
 * 例如,在GetSnapshotData()调用后,将不存在procarray状态来防止maybe_needed变得32bit XID中存在的过老现象。
 *
 * The typedef is in the header.
 */
struct GlobalVisState
{
    /* XIDs >= are considered running by some backend */
    //大于等于该值的XIDs:其他活动backend可见
    FullTransactionId definitely_needed;
    /* XIDs < are not considered to be running by any backend */
    //小于该值的XIDs:不再视为正在运行,均可见
    FullTransactionId maybe_needed;
};
/*
 * A 64 bit value that contains an epoch and a TransactionId.  This is
 * wrapped in a struct to prevent implicit conversion to/from TransactionId.
 * Not all values represent valid normal XIDs.
 * 号外:PG 14已支持64 bit XID,令人厌烦的XID回卷问题不复存在
 */
typedef struct FullTransactionId
{
    uint64        value;
} FullTransactionId;

二、源码解读

N/A

三、跟踪分析

SESSION

-- session 1
[local:/opt/data5014]:5014 pgmaster@testdb=# begin;
BEGIN
Time: 0.370 ms
[local:/opt/data5014]:5014 pgmaster@testdb=#* insert into t1 values(1);
INSERT 0 1
Time: 0.805 ms
[local:/opt/data5014]:5014 pgmaster@testdb=#*  select txid_current();
 txid_current
--------------
          541
(1 row)
Time: 0.596 ms
[local:/opt/data5014]:5014 pgmaster@testdb=#*
-- session 2(GDB跟踪的进程)
[local:/opt/data5014]:5014 pgmaster@testdb=# begin;
BEGIN
Time: 0.299 ms
[local:/opt/data5014]:5014 pgmaster@testdb=#* select * from t1;
-- session 3
[local:/opt/data5014]:5014 pgmaster@testdb=# select txid_current();
 txid_current
--------------
          542
(1 row)
Time: 4.314 ms
[local:/opt/data5014]:5014 pgmaster@testdb=#

跟踪分析


(gdb) b GetSnapshotData
Breakpoint 1 at 0x924965: file procarray.c, line 2111.
(gdb) c
Continuing.
Breakpoint 1, GetSnapshotData (snapshot=0x103adc0 ) at procarray.c:2111
2111            ProcArrayStruct *arrayP = procArray;
//ProcArrayStruct结构体
(gdb) p *procArray
$3 = {numProcs = 4, maxProcs = 122, maxKnownAssignedXids = 7930, numKnownAssignedXids = 0, tailKnownAssignedXids = 0, headKnownAssignedXids = 0, known_assigned_xids_lck = 0 '\000', lastOverflowedXid = 0,
  replication_slot_xmin = 0, replication_slot_catalog_xmin = 0, pgprocnos = 0x7fee7e2148a8}
(gdb) p *procArray->pgprocnos
$4 = 97
(gdb) n
2112            TransactionId *other_xids = ProcGlobal->xids;
//PROC_HDR结构体
(gdb) p *ProcGlobal
$5 = {allProcs = 0x7fee7e1f9300, xids = 0x7fee7e214480, subxidStates = 0x7fee7e214680, statusFlags = 0x7fee7e214780 "", allProcCount = 126, freeProcs = 0x7fee7e20dd00, autovacFreeProcs = 0x7fee7e20f1a0,
  bgworkerFreeProcs = 0x7fee7e210d20, walsenderFreeProcs = 0x7fee7e2132f0, procArrayGroupFirst = {value = 2147483647}, clogGroupFirst = {value = 2147483647}, walwriterLatch = 0x7fee7e2139f4,
  checkpointerLatch = 0x7fee7e213684, spins_per_delay = 208, startupProc = 0x0, startupProcPid = 0, startupBufferPinWaitBufId = -1}
//PGPROC结构体数组(这里打印了数组的第一个元素)  
(gdb) p *ProcGlobal->allProcs
$6 = {links = {prev = 0x0, next = 0x0}, procgloballist = 0x7fee7e1f92a8, sem = 0x7fee75684038, waitStatus = PROC_WAIT_STATUS_OK, procLatch = {is_set = 0, is_shared = true, owner_pid = 0}, xid = 0, xmin = 0, lxid = 0,
  pid = 0, pgxactoff = 0, pgprocno = 0, backendId = 0, databaseId = 0, roleId = 0, tempNamespaceId = 0, isBackgroundWorker = false, recoveryConflictPending = false, lwWaiting = false, lwWaitMode = 0 '\000', lwWaitLink = {
    next = 0, prev = 0}, cvWaitLink = {next = 0, prev = 0}, waitLock = 0x0, waitProcLock = 0x0, waitLockMode = 0, heldLocks = 0, waitStart = {value = 0}, delayChkpt = false, statusFlags = 0 '\000', waitLSN = 0,
  syncRepState = 0, syncRepLinks = {prev = 0x0, next = 0x0}, myProcLocks = {{prev = 0x7fee7e1f93b8, next = 0x7fee7e1f93b8}, {prev = 0x7fee7e1f93c8, next = 0x7fee7e1f93c8}, {prev = 0x7fee7e1f93d8, next = 0x7fee7e1f93d8}, {
      prev = 0x7fee7e1f93e8, next = 0x7fee7e1f93e8}, {prev = 0x7fee7e1f93f8, next = 0x7fee7e1f93f8}, {prev = 0x7fee7e1f9408, next = 0x7fee7e1f9408}, {prev = 0x7fee7e1f9418, next = 0x7fee7e1f9418}, {prev = 0x7fee7e1f9428,
      next = 0x7fee7e1f9428}, {prev = 0x7fee7e1f9438, next = 0x7fee7e1f9438}, {prev = 0x7fee7e1f9448, next = 0x7fee7e1f9448}, {prev = 0x7fee7e1f9458, next = 0x7fee7e1f9458}, {prev = 0x7fee7e1f9468,
      next = 0x7fee7e1f9468}, {prev = 0x7fee7e1f9478, next = 0x7fee7e1f9478}, {prev = 0x7fee7e1f9488, next = 0x7fee7e1f9488}, {prev = 0x7fee7e1f9498, next = 0x7fee7e1f9498}, {prev = 0x7fee7e1f94a8,
      next = 0x7fee7e1f94a8}}, subxidStatus = {count = 0 '\000', overflowed = false}, subxids = {xids = {0 }}, procArrayGroupMember = false, procArrayGroupNext = {value = 2147483647},
  procArrayGroupMemberXid = 0, wait_event_info = 0, clogGroupMember = false, clogGroupNext = {value = 2147483647}, clogGroupMemberXid = 0, clogGroupMemberXidStatus = 0, clogGroupMemberPage = 0, clogGroupMemberLsn = 0,
  fpInfoLock = {tranche = 60, state = {value = 536870912}, waiters = {head = 2147483647, tail = 2147483647}}, fpLockBits = 0, fpRelId = {0 }, fpVXIDLock = false, fpLocalTransactionId = 0,
  lockGroupLeader = 0x0, lockGroupMembers = {head = {prev = 0x7fee7e1f9650, next = 0x7fee7e1f9650}}, lockGroupLink = {prev = 0x0, next = 0x0}}
(gdb) p *ProcGlobal->xids
$7 = 0
(gdb) n
2115            size_t          count = 0;
(gdb)
2116            int                     subcount = 0;
(gdb)
2117            bool            suboverflowed = false;
(gdb)
//无流复制
2124            TransactionId replication_slot_xmin = InvalidTransactionId;
(gdb)
2125            TransactionId replication_slot_catalog_xmin = InvalidTransactionId;
(gdb)
2127            Assert(snapshot != NULL);
(gdb)
2140            if (snapshot->xip == NULL)
(gdb)
//已共享模式获取ProcArrayLock
2165            LWLockAcquire(ProcArrayLock, LW_SHARED);
(gdb)
//判断快照是否可以重用
2167            if (GetSnapshotDataReuse(snapshot))
(gdb) step
//进入GetSnapshotDataReuse
GetSnapshotDataReuse (snapshot=0x103adc0 ) at procarray.c:2030
2030            Assert(LWLockHeldByMe(ProcArrayLock));
//快照信息
(gdb) p *snapshot
$8 = {snapshot_type = SNAPSHOT_MVCC, xmin = 540, xmax = 540, xip = 0x2c15080, xcnt = 0, subxip = 0x2c6a350, subxcnt = 0, suboverflowed = false, takenDuringRecovery = false, copied = false, curcid = 0,
  speculativeToken = 0, vistest = 0x0, active_count = 0, regd_count = 0, ph_node = {first_child = 0x0, next_sibling = 0x0, prev_or_parent = 0x0}, whenTaken = 0, lsn = 0, snapXactCompletionCount = 1}
(gdb) n
2032            if (unlikely(snapshot->snapXactCompletionCount == 0))
(gdb)
2035            curXactCompletionCount = ShmemVariableCache->xactCompletionCount;
(gdb)
//两者不等,不能重用
2036            if (curXactCompletionCount != snapshot->snapXactCompletionCount)
//共享变量缓存
//最后一个已完成事务的xid=542,因此xmax=543
(gdb) p *ShmemVariableCache
$9 = {nextOid = 16388, oidCount = 8189, nextXid = {value = 543}, oldestXid = 532, xidVacLimit = 200000532, xidWarnLimit = 2107484179, xidStopLimit = 2144484179, xidWrapLimit = 2147484179, oldestXidDB = 1,
  oldestCommitTsXid = 0, newestCommitTsXid = 0, latestCompletedXid = {value = 542}, xactCompletionCount = 3, oldestClogXid = 532}
(gdb) n
2037                    return false;
(gdb)
2073    }
(gdb)
GetSnapshotData (snapshot=0x103adc0 ) at procarray.c:2173
2173            latest_completed = ShmemVariableCache->latestCompletedXid;
(gdb)
2174            mypgxactoff = MyProc->pgxactoff;
(gdb)
2175            myxid = other_xids[mypgxactoff];
//获取快照的进程PGPROC结构体信息
(gdb) p *MyProc
$10 = {links = {prev = 0x0, next = 0x0}, procgloballist = 0x7fee7e1f92a8, sem = 0x7fee756871b8, waitStatus = PROC_WAIT_STATUS_OK, procLatch = {is_set = 0, is_shared = true, owner_pid = 2593}, xid = 0, xmin = 0,
  lxid = 10, pid = 2593, pgxactoff = 2, pgprocno = 99, backendId = 3, databaseId = 16384, roleId = 10, tempNamespaceId = 0, isBackgroundWorker = false, recoveryConflictPending = false, lwWaiting = false,
  lwWaitMode = 0 '\000', lwWaitLink = {next = 0, prev = 0}, cvWaitLink = {next = 0, prev = 0}, waitLock = 0x0, waitProcLock = 0x0, waitLockMode = 0, heldLocks = 0, waitStart = {value = 0}, delayChkpt = false,
  statusFlags = 0 '\000', waitLSN = 0, syncRepState = 0, syncRepLinks = {prev = 0x0, next = 0x0}, myProcLocks = {{prev = 0x7fee7e20e808, next = 0x7fee7e20e808}, {prev = 0x7fee7e20e818, next = 0x7fee7e20e818}, {
      prev = 0x7fee7e20e828, next = 0x7fee7e20e828}, {prev = 0x7fee7e20e838, next = 0x7fee7e20e838}, {prev = 0x7fee7e20e848, next = 0x7fee7e20e848}, {prev = 0x7fee7e20e858, next = 0x7fee7e20e858}, {prev = 0x7fee7e20e868,
      next = 0x7fee7e20e868}, {prev = 0x7fee7e20e878, next = 0x7fee7e20e878}, {prev = 0x7fee7e20e888, next = 0x7fee7e20e888}, {prev = 0x7fee7e20e898, next = 0x7fee7e20e898}, {prev = 0x7fee7e20e8a8,
      next = 0x7fee7e20e8a8}, {prev = 0x7fee7e20e8b8, next = 0x7fee7e20e8b8}, {prev = 0x7fee7e20e8c8, next = 0x7fee7e20e8c8}, {prev = 0x7fee7e20e8d8, next = 0x7fee7e20e8d8}, {prev = 0x7fee7e20e8e8,
      next = 0x7fee7e20e8e8}, {prev = 0x7fee7e20e8f8, next = 0x7fee7e20e8f8}}, subxidStatus = {count = 0 '\000', overflowed = false}, subxids = {xids = {0 }}, procArrayGroupMember = false,
  procArrayGroupNext = {value = 2147483647}, procArrayGroupMemberXid = 0, wait_event_info = 0, clogGroupMember = false, clogGroupNext = {value = 2147483647}, clogGroupMemberXid = 0, clogGroupMemberXidStatus = 0,
  clogGroupMemberPage = -1, clogGroupMemberLsn = 0, fpInfoLock = {tranche = 60, state = {value = 536870912}, waiters = {head = 2147483647, tail = 2147483647}}, fpLockBits = 0, fpRelId = {0 , 2703,
    1247}, fpVXIDLock = true, fpLocalTransactionId = 10, lockGroupLeader = 0x0, lockGroupMembers = {head = {prev = 0x7fee7e20eaa0, next = 0x7fee7e20eaa0}}, lockGroupLink = {prev = 0x0, next = 0x0}}
(gdb) p mypgxactoff
$11 = 2
(gdb) p other_xids[mypgxactoff]
$12 = 0
(gdb) n
2176            Assert(myxid == MyProc->xid);
(gdb) p MyProc->xid
$13 = 0
(gdb) n
2178            oldestxid = ShmemVariableCache->oldestXid;
(gdb)
//curXactCompletionCount = 3
2179            curXactCompletionCount = ShmemVariableCache->xactCompletionCount;
(gdb)
2182            xmax = XidFromFullTransactionId(latest_completed);
(gdb)
2183            TransactionIdAdvance(xmax);
(gdb) p xmax
$14 = 542
(gdb) n
2184            Assert(TransactionIdIsNormal(xmax));
//xmax = 543(542 + 1)
(gdb) p xmax
$15 = 543
(gdb) n
//初始化,xmin = xmax
2187            xmin = xmax;
(gdb)
2190            if (TransactionIdIsNormal(myxid) && NormalTransactionIdPrecedes(myxid, xmin))
(gdb) p myxid
$16 = 0
(gdb) n
2193            snapshot->takenDuringRecovery = RecoveryInProgress();
(gdb)
2195            if (!snapshot->takenDuringRecovery)
(gdb)
//不在恢复过程中
2197                    size_t          numProcs = arrayP->numProcs;
(gdb)
2198                    TransactionId *xip = snapshot->xip;
(gdb) p numProcs
$17 = 5
(gdb) n
2199                    int                *pgprocnos = arrayP->pgprocnos;
(gdb) p *xip
$18 = 2258962920
(gdb) p (int)*xip
$19 = -2036004376
(gdb) n
2200                    XidCacheStatus *subxidStates = ProcGlobal->subxidStates;
(gdb)
2201                    uint8      *allStatusFlags = ProcGlobal->statusFlags;
(gdb)
//遍历PGPROC数组,这是成本比较高的逻辑
2207                    for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
(gdb)
2210                            TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
(gdb)
2213                            Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
(gdb) p xid
$20 = 0
(gdb) n
//无效的xid,继续下一循环
2219                            if (likely(xid == InvalidTransactionId))
(gdb)
2220                                    continue;
(gdb)
2207                    for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
(gdb)
2210                            TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
(gdb)
2213                            Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
(gdb) p xid
$21 = 541
//这是有效的xid
(gdb) n
2219                            if (likely(xid == InvalidTransactionId))
(gdb)
//不是当前的事务,是其他事务xid
2227                            if (pgxactoff == mypgxactoff)
(gdb)
2236                            Assert(TransactionIdIsNormal(xid));
(gdb)
2243                            if (!NormalTransactionIdPrecedes(xid, xmax))
(gdb) p xid
$22 = 541
(gdb) p xmax
$23 = 543
(gdb) n
2250                            statusFlags = allStatusFlags[pgxactoff];
(gdb)
2251                            if (statusFlags & (PROC_IN_LOGICAL_DECODING | PROC_IN_VACUUM))
(gdb) p statusFlags
$24 = 0 '\000'
(gdb) n
2254                            if (NormalTransactionIdPrecedes(xid, xmin))
(gdb)
2255                                    xmin = xid;
(gdb) p xmin
$25 = 543
(gdb) p xid
$26 = 541
(gdb) n
//该事务还没有完成,放在xip数组中
2258                            xip[count++] = xid;
(gdb)
2275                            if (!suboverflowed)
(gdb) p xip[0]
$27 = 541
(gdb) n
2278                                    if (subxidStates[pgxactoff].overflowed)
(gdb)
2282                                            int                     nsubxids = subxidStates[pgxactoff].count;
(gdb)
2284                                            if (nsubxids > 0)
(gdb)
2207                    for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
(gdb)
2210                            TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
(gdb)
2213                            Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
(gdb) p xid
$29 = 0
(gdb) n
2219                            if (likely(xid == InvalidTransactionId))
(gdb)
2220                                    continue;
(gdb)
2207                    for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
(gdb)
2210                            TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
(gdb)
2213                            Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
(gdb) p xid
$30 = 0
(gdb) n
2219                            if (likely(xid == InvalidTransactionId))
(gdb)
2220                                    continue;
(gdb)
2207                    for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
(gdb)
2210                            TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
(gdb)
2213                            Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
(gdb) p xid
$31 = 0
(gdb) n
2219                            if (likely(xid == InvalidTransactionId))
(gdb)
2220                                    continue;
//完成PGPROC数组的遍历
(gdb)
2207                    for (size_t pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
(gdb)
2344            replication_slot_xmin = procArray->replication_slot_xmin;
(gdb)
2345            replication_slot_catalog_xmin = procArray->replication_slot_catalog_xmin;
(gdb)
2347            if (!TransactionIdIsValid(MyProc->xmin))
(gdb)
2348                    MyProc->xmin = TransactionXmin = xmin;
(gdb)
//释放锁
2350            LWLockRelease(ProcArrayLock);
(gdb)
2365                    oldestfxid = FullXidRelativeTo(latest_completed, oldestxid);
(gdb)
2369                            TransactionIdRetreatedBy(xmin, vacuum_defer_cleanup_age);
(gdb) p oldestfxid
$32 = {value = 532}
(gdb) p latest_completed
$33 = {value = 542}
(gdb) n
2368                    def_vis_xid_data =
(gdb) p xmin
$34 = 541
(gdb) n
2372                    def_vis_xid_data =
(gdb)
2379                    def_vis_xid = def_vis_xid_data;
(gdb)
2385                    def_vis_xid =
(gdb)
2388                    def_vis_fxid = FullXidRelativeTo(latest_completed, def_vis_xid);
(gdb)
2389                    def_vis_fxid_data = FullXidRelativeTo(latest_completed, def_vis_xid_data);
(gdb) p def_vis_fxid
$35 = {value = 541}
(gdb) n
2396                    GlobalVisSharedRels.definitely_needed =
(gdb)
2399                    GlobalVisCatalogRels.definitely_needed =
(gdb)
2402                    GlobalVisDataRels.definitely_needed =
(gdb)
2406                    if (TransactionIdIsNormal(myxid))
//definitely_needed : 大于等于该值的XIDs:其他活动backend可见
//maybe_needed : 小于该值的XIDs:不再视为正在运行,均可见
(gdb) p GlobalVisDataRels
$36 = {definitely_needed = {value = 541}, maybe_needed = {value = 532}}
(gdb) n
2411                            GlobalVisTempRels.definitely_needed = latest_completed;
(gdb)
2412                            FullTransactionIdAdvance(&GlobalVisTempRels.definitely_needed);
(gdb)
2423                    GlobalVisSharedRels.maybe_needed =
(gdb)
2426                    GlobalVisCatalogRels.maybe_needed =
(gdb)
2429                    GlobalVisDataRels.maybe_needed =
(gdb)
2433                    GlobalVisTempRels.maybe_needed = GlobalVisTempRels.definitely_needed;
(gdb)
2436            RecentXmin = xmin;
//临时表
(gdb) p GlobalVisTempRels
$37 = {definitely_needed = {value = 543}, maybe_needed = {value = 543}}
(gdb) n
2437            Assert(TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin));
(gdb)
2439            snapshot->xmin = xmin;
(gdb)
2440            snapshot->xmax = xmax;
(gdb)
2441            snapshot->xcnt = count;
(gdb)
2442            snapshot->subxcnt = subcount;
(gdb)
2443            snapshot->suboverflowed = suboverflowed;
(gdb)
2444            snapshot->snapXactCompletionCount = curXactCompletionCount;
(gdb)
2446            snapshot->curcid = GetCurrentCommandId(false);
(gdb)
2452            snapshot->active_count = 0;
(gdb)
2453            snapshot->regd_count = 0;
(gdb)
2454            snapshot->copied = false;
(gdb)
2456            GetSnapshotDataInitOldSnapshot(snapshot);
(gdb)
2458            return snapshot;
(gdb)
2459    }
//最后获得的快照信息
(gdb) p *snapshot
$38 = {snapshot_type = SNAPSHOT_MVCC, xmin = 541, xmax = 543, xip = 0x2c15080, xcnt = 1, subxip = 0x2c6a350, subxcnt = 0, suboverflowed = false, takenDuringRecovery = false, copied = false, curcid = 0,
  speculativeToken = 0, vistest = 0x0, active_count = 0, regd_count = 0, ph_node = {first_child = 0x0, next_sibling = 0x0, prev_or_parent = 0x0}, whenTaken = 0, lsn = 0, snapXactCompletionCount = 3}
(gdb)

四、参考资料

1. Improving connection scalability: GetSnapshotData()

请使用浏览器的分享功能分享到微信等