理解 PostgreSQL 中的 Virtual Transaction ID(VXID)
文章目录
- 什么是 PID(Process Identifier)
- 什么是 Virtual Transaction ID(VXID)
- 如何查看我们的 Backend ID
- VXID 会递增变化
- 如何获得真正的 XID(Transaction ID)
- 事务ID的范围与特殊值
- 为什么 使用Virtual XID ?
- 总结
在 PostgreSQL 数据库中,事务管理是核心功能之一,而事务ID(Transaction ID,简称 XID)和虚拟事务ID(Virtual Transaction ID,简称 VXID)是事务管理的关键组成部分。本文通过一系列实验,深入探讨 PostgreSQL 中的进程标识符(PID)、虚拟事务ID(VXID)、后端ID(Backend ID)以及真实事务ID(XID)的概念、获取方式及其行为特性。
什么是 PID(Process Identifier)
在 PostgreSQL 中,每个客户端连接都对应一个后端进程。我们可以通过如下命令查看当前连接的进程 ID(PID):
SELECT pg_backend_pid();
示例输出:
pg_backend_pid
----------------121627
这个 121627 就是当前会话的进程 ID。
什么是 Virtual Transaction ID(VXID)
VXID 是 PostgreSQL 用来标识当前事务(包括只读事务)的轻量级标识符,它的格式是:
backend_id/backend-local_xid
比如:
SELECT virtualtransaction AS vxid, transactionid::text
FROM pg_locks
WHERE pid = pg_backend_pid()
ORDER BY 1, 2
LIMIT 1;
输出:
vxid | transactionid
------+---------------3/88 |
这里的 3 是 Backend ID,88 是backend-local xid(该后端上的虚拟本地事务号)。VXID 是轻量级的标识,不涉及 WAL 日志,因此非常适合用于锁和并发控制。
如何查看我们的 Backend ID
Backend ID 是 PostgreSQL 在当前实例中为每个连接分配的内部编号,最大backend ID 由受限于max_connections 参数的设置。
可以用如下查询获取:
SELECT *
FROM pg_stat_get_backend_idset() AS t(id)
WHERE pg_stat_get_backend_pid(id) = pg_backend_pid();
输出:
id
----3
这与我们之前看到的 VXID 中的前缀 3 是一致的。
VXID 会递增变化
每当你开始一个新的事务或执行某些需要内部事务的语句(比如 ANALYZE),VXID 的后缀部分(local xid)就会递增。例如多次运行以下查询:
SELECT virtualtransaction AS vxid, transactionid::text
FROM pg_locks
WHERE pid = pg_backend_pid()
ORDER BY 1, 2
LIMIT 1;
第一次查询:
vxid | transactionid
------+---------------3/91 |
第二次查询:
vxid | transactionid
------+---------------3/92 |
第三次查询:
vxid | transactionid
------+---------------3/93 |
查询结果表明:
- VXID 的格式为 3/91、3/92、3/93,其中 3 是backend ID,91、92、93 是backend-local xid 。
- 每次执行查询,local_xid 都会递增,表明 PostgreSQL 为每次查询分配了一个新的虚拟事务ID,即使这些查询未显式开启事务。
此时 transactionid 为空,说明这些查询未分配真实的事务ID(XID),仅使用了虚拟事务ID。
如何获得真正的 XID(Transaction ID)
XID 是 PostgreSQL 中用于 MVCC 的实际事务标识符。只有在执行某些写操作或显式请求事务 ID 时才会分配 XID。
步骤如下:
BEGIN WORK;
SELECT virtualtransaction AS vxid, transactionid::text
FROM pg_locks
WHERE pid = pg_backend_pid()
ORDER BY 1, 2
LIMIT 1;
初始时可能只看到 VXID,例如:
vxid | transactionid
------+---------------
3/103 |
执行如下操作:
ANALYZE pg_language;
再查询一次:
vxid | transactionid
------+---------------
3/103| 746
此时分配了真正的 XID 746。你也可以直接查看:
SELECT txid_current();
返回
txid_current
--------------746
最后终止事务
COMMIT;
以上我们演示了透过begin block显示获得xid的过程,或者我们也能透过:
postgres=*# BEGIN WORK;
BEGIN
postgres=*# SELECT virtualtransaction AS vxid, transactionid::text
FROM pg_locks
WHERE pid = pg_backend_pid()
ORDER BY 1, 2 Limit 1;vxid | transactionid
-------+---------------3/105 |
(1 row)postgres=*# SELECT txid_current();txid_current
--------------747
(1 row)postgres=*# SELECT virtualtransaction AS vxid, transactionid::text
FROM pg_locks
WHERE pid = pg_backend_pid()
ORDER BY 1, 2
LIMIT 1;vxid | transactionid
-------+---------------3/105 | 747COMMIT;
COMMIT
事务ID的范围与特殊值
根据实验和 PostgreSQL 文档:
- XID 范围:事务ID 从 3 开始,最大值为 2^32(约 40 亿)。
- 特殊值:
- 0:无效的事务ID。
- 1 和 2:用于设置冻结事务ID(FrozenXID),分别表示committed和aboarted的事务。
- VXID 递增:每次数据库操作(如查询或事务)都会递增 local_xid,即使未分配真实 XID
为什么 使用Virtual XID ?
-
VXID 是 PostgreSQL 中实现轻量级锁的重要机制之一。例如在并发控制中,pg_locks 中会记录 VXID,便于判断是否存在锁冲突。VXID 不需要写入 WAL,不会影响性能。
-
当你只是执行读操作时,VXID 会存在,但 XID 不会分配,从而减少事务膨胀和 XID wraparound 风险。
-
虚拟事务(Virtual Transaction不是数据库领域的通用概念,它不是面向用户的概念,而是PostgreSQL(简称PG)内部的一种纯技术机制。
在PG中,所有隐式或显式的只读事务都会被视为虚拟事务。
PG引入这个概念的主要原因是性能优化:在较早的版本中(大约在9.0版本之前),PG尚未引入虚拟事务的概念,而PG中的所有操作都必须属于某个隐式或显式事务,因此就涉及到**分配XID(事务ID)**的问题。但是在XID的分配过程中存在几个衍生问题:
- XID的分配过程是加锁的(在代码中是 XidGenLock),一旦涉及锁,就会产生竞争,进而影响性能。
- XID会频繁增长。
后来PG开发者发现,对于只读事务(无论是隐式还是显式),其实并不需要分配XID。因为只读事务不会修改数据,即使为它们分配了XID,这个XID在存储层的事务管理中也不会被真正使用。而且,对于一个OLTP系统来说,大多数应用场景下读操作远多于写操作。因此,为了优化性能,PG在只读事务中不再分配XID,而是引入了虚拟事务ID的概念。分配虚拟事务ID的成本比XID要低得多。
总结
标识符 | 描述 |
---|---|
PID | 当前连接对应的进程 ID |
Backend ID | PostgreSQL 内部分配给连接的编号 |
VXID | 虚拟事务 ID,格式为 backend_id/local_xid |
XID | 实际事务 ID,仅在需要时才分配 |
VXID 是 PostgreSQL 并发和锁管理中的关键机制,它轻量、高效,在实际应用中非常重要。