pg_locks.virtualxid & transactionid

5 minute read

背景

在pg_locks中,你可能会注意到两个字段virtualxid和transactionid,这两个字段到底有什么分别呢?
Table 48-61. pg_locks Columns

Name Type References Description
locktype text - Type of the lockable object: relation, extend, page, tuple, transactionid, virtualxid, object, userlock, or advisory
database oid pg_database.oid OID of the database in which the lock target exists, or zero if the target is a shared object, or null if the target is a transaction ID
relation oid pg_class.oid OID of the relation targeted by the lock, or null if the target is not a relation or part of a relation
page integer - Page number targeted by the lock within the relation, or null if the target is not a relation page or tuple
tuple smallint - Tuple number targeted by the lock within the page, or null if the target is not a tuple
virtualxid text - Virtual ID of the transaction targeted by the lock, or null if the target is not a virtual transaction ID
transactionid xid - ID of the transaction targeted by the lock, or null if the target is not a transaction ID
classid oid pg_class.oid OID of the system catalog containing the lock target, or null if the target is not a general database object
objid oid any OID column OID of the lock target within its system catalog, or null if the target is not a general database object
objsubid smallint - Column number targeted by the lock (the classid and objid refer to the table itself), or zero if the target is some other general database object, or null if the target is not a general database object
virtualtransaction text - Virtual ID of the transaction that is holding or awaiting this lock
pid integer - Process ID of the server process holding or awaiting this lock, or null if the lock is held by a prepared transaction
mode text - Name of the lock mode held or desired by this process (see Section 13.3.1 and Section 13.2.3)
granted boolean - True if lock is held, false if lock is awaited
fastpath boolean - True if lock was taken via fast path, false if taken via main lock table

从字义上来看,一个是虚拟事务号,一个是事务号。

实际上他们表达的意思也是不一样的,虚拟事务号由两个部分组成,分别是backendid和local transaction id。

例如:

postgres=# select * from pg_locks where locktype='virtualxid';  
-[ RECORD 1 ]------+--------------  
locktype           | virtualxid  
database           |   
relation           |   
page               |   
tuple              |   
virtualxid         | 2/412048  
transactionid      |   
classid            |   
objid              |   
objsubid           |   
virtualtransaction | 2/412048  
pid                | 24401  
mode               | ExclusiveLock  
granted            | t  
fastpath           | t  
-[ RECORD 2 ]------+--------------  
locktype           | virtualxid  
database           |   
relation           |   
page               |   
tuple              |   
virtualxid         | 3/73709664  
transactionid      |   
classid            |   
objid              |   
objsubid           |   
virtualtransaction | 3/73709664  
pid                | 24554  
mode               | ExclusiveLock  
granted            | t  
fastpath           | t  
  
postgres=# select * from pg_locks where pid=24401;  
   locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  
  pid  |        mode         | granted | fastpath   
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+  
-------+---------------------+---------+----------  
 relation      |    12944 |    11181 |      |       |            |               |         |       |          | 2/412049           |  
 24401 | AccessShareLock     | t       | t  
 virtualxid    |          |          |      |       | 2/412049   |               |         |       |          | 2/412049           |  
 24401 | ExclusiveLock       | t       | t  
 relation      |    12944 |    60687 |      |       |            |               |         |       |          | 2/412049           |  
 24401 | AccessExclusiveLock | t       | f  
 object        |    12944 |          |      |       |            |               |    2615 |  2200 |        0 | 2/412049           |  
 24401 | AccessShareLock     | t       | f  
 transactionid |          |          |      |       |            |    1662669457 |         |       |          | 2/412049           |  
 24401 | ExclusiveLock       | t       | f  
(5 rows)  

他们分别对应的锁TAG如下:

src/include/storage/lock.h

	LOCKTAG_TRANSACTION,            /* transaction (for waiting for xact done) */  
        /* ID info for a transaction is its TransactionId */  
        LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */  
        /* ID info for a virtual transaction is its VirtualTransactionId */  

虚拟事务号的数据结构如下:

/*  
 * Top-level transactions are identified by VirtualTransactionIDs comprising  
 * the BackendId of the backend running the xact, plus a locally-assigned  
 * LocalTransactionId.  These are guaranteed unique over the short term,  
 * but will be reused after a database restart; hence they should never  
 * be stored on disk.  
 *  
 * Note that struct VirtualTransactionId can not be assumed to be atomically  
 * assignable as a whole.  However, type LocalTransactionId is assumed to  
 * be atomically assignable, and the backend ID doesn't change often enough  
 * to be a problem, so we can fetch or assign the two fields separately.  
 * We deliberately refrain from using the struct within PGPROC, to prevent  
 * coding errors from trying to use struct assignment with it; instead use  
 * GET_VXID_FROM_PGPROC().  
 */  
typedef struct  
{  
        BackendId       backendId;              /* determined at backend startup */  
        LocalTransactionId localTransactionId;          /* backend-local transaction  
                                                                                                 * id */  
} VirtualTransactionId;  
  
#define InvalidLocalTransactionId               0  
#define LocalTransactionIdIsValid(lxid) ((lxid) != InvalidLocalTransactionId)  
#define VirtualTransactionIdIsValid(vxid) \  
        (((vxid).backendId != InvalidBackendId) && \  
         LocalTransactionIdIsValid((vxid).localTransactionId))  
#define VirtualTransactionIdEquals(vxid1, vxid2) \  
        ((vxid1).backendId == (vxid2).backendId && \  
         (vxid1).localTransactionId == (vxid2).localTransactionId)  
#define SetInvalidVirtualTransactionId(vxid) \  
        ((vxid).backendId = InvalidBackendId, \  
         (vxid).localTransactionId = InvalidLocalTransactionId)  
#define GET_VXID_FROM_PGPROC(vxid, proc) \  
        ((vxid).backendId = (proc).backendId, \  
         (vxid).localTransactionId = (proc).lxid)  

本地事务号和事务号又有什么分别呢?实际上一个是用来表示本地事务的,并且本地事务号不会存储在磁盘中,只存在于内存中。而事务号则是存储在磁盘中的,属于持久化的值。

src/include/c.h

typedef uint32 TransactionId;  
  
typedef uint32 LocalTransactionId;  
  
typedef uint32 SubTransactionId;  
  
#define InvalidSubTransactionId         ((SubTransactionId) 0)  
#define TopSubTransactionId                     ((SubTransactionId) 1)  
  
/* MultiXactId must be equivalent to TransactionId, to fit in t_xmax */  
typedef TransactionId MultiXactId;  
  
typedef uint32 MultiXactOffset;  
  
typedef uint32 CommandId;  
  
#define FirstCommandId  ((CommandId) 0)  
#define InvalidCommandId        (~(CommandId)0)  

获得本地事务号的函数如下,不需要vacuum,因为不会持久化到磁盘,也不用于MVCC,所以直接轮询使用是没问题的,见如下函数:

src/backend/storage/ipc/sinvaladt.c

/*  
 * GetNextLocalTransactionId --- allocate a new LocalTransactionId  
 *  
 * We split VirtualTransactionIds into two parts so that it is possible  
 * to allocate a new one without any contention for shared memory, except  
 * for a bit of additional overhead during backend startup/shutdown.  
 * The high-order part of a VirtualTransactionId is a BackendId, and the  
 * low-order part is a LocalTransactionId, which we assign from a local  
 * counter.  To avoid the risk of a VirtualTransactionId being reused  
 * within a short interval, successive procs occupying the same backend ID  
 * slot should use a consecutive sequence of local IDs, which is implemented  
 * by copying nextLocalTransactionId as seen above.  
 */  
LocalTransactionId  
GetNextLocalTransactionId(void)  
{  
        LocalTransactionId result;  
  
        /* loop to avoid returning InvalidLocalTransactionId at wraparound */  
        do  
        {  
                result = nextLocalTransactionId++;  
        } while (!LocalTransactionIdIsValid(result));  
  
        return result;  
}  

那么虚拟事务号到底有什么用呢?

举一些例子,

1. 因为虚拟事务号是在内存中管理的,所以在处理锁冲突时效率更高,可以用于唯一标示发生锁冲突的事务对象。

src/backend/storage/lmgr/lock.c

VirtualTransactionId *  
GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)  
{  
。。。。。。  

2. 同时应用于standby的查询和恢复的锁冲突标示。

3. 还可以用于标示检查点和用户执行的查询之间的锁冲突。

总的来说,虚拟事务号就是用来标示锁冲突的对象的。

参考

1. src/backend/storage/ipc/sinvaladt.c

2. src/include/c.h

3. src/include/storage/lock.h

4. src/backend/storage/lmgr/lock.c

Flag Counter

digoal’s 大量PostgreSQL文章入口