当前位置: 首页 > web >正文

理解 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 文档:

  1. XID 范围:事务ID 从 3 开始,最大值为 2^32(约 40 亿)。
  2. 特殊值:
  • 0:无效的事务ID。
  • 1 和 2:用于设置冻结事务ID(FrozenXID),分别表示committed和aboarted的事务。
  1. 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的分配过程中存在几个衍生问题:

  1. XID的分配过程是加锁的(在代码中是 XidGenLock),一旦涉及锁,就会产生竞争,进而影响性能。
  2. XID会频繁增长。
    后来PG开发者发现,对于只读事务(无论是隐式还是显式),其实并不需要分配XID。因为只读事务不会修改数据,即使为它们分配了XID,这个XID在存储层的事务管理中也不会被真正使用。而且,对于一个OLTP系统来说,大多数应用场景下读操作远多于写操作。因此,为了优化性能,PG在只读事务中不再分配XID,而是引入了虚拟事务ID的概念。分配虚拟事务ID的成本比XID要低得多。

总结

标识符描述
PID当前连接对应的进程 ID
Backend IDPostgreSQL 内部分配给连接的编号
VXID虚拟事务 ID,格式为 backend_id/local_xid
XID实际事务 ID,仅在需要时才分配

VXID 是 PostgreSQL 并发和锁管理中的关键机制,它轻量、高效,在实际应用中非常重要。

http://www.xdnf.cn/news/13395.html

相关文章:

  • 123123
  • 【Java】动态加载数据库驱动:灵活连接
  • IMX6ULL--EPIT 定时器理论
  • 打卡第41天:训练和测试的规范写法
  • C/C++八股文
  • 面试题 - 日志(Java)
  • 网络爬虫学习心得
  • 智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
  • 二维数组判断空的情况
  • uniapp自定义导航栏,采用粘性定位
  • STM32 PID控制
  • python打卡训练营打卡记录day50
  • 林清轩以研发为核,用专利技术筑就高端国货护肤壁垒
  • 函数02 day11
  • AI赋能农业
  • 第十六章 I2C
  • 【PhysUnits】17.6 Unit基础结构(unit.rs)
  • <component :is=““>
  • CentOS7下的ZooKeeper部署
  • 55. Jump Game
  • Redis持久化策略介绍,以及如何选择?
  • 第二十四章 通用同步异步收发器(USART)
  • java异步编程难题拆解
  • Java 中 switch-case 语句的执行逻辑与避坑指南
  • Java判断规则工具类
  • 工作日记总结-transaction is aborted, commands ignored until end of transaction block
  • [软件测试]:什么是自动化测试?selenium+webdriver-manager的安装,实现你的第一个脚本
  • Kotlin基础语法二
  • 大数据学习(136)-数据埋点
  • 玄机 日志分析-Tomcat日志分析 WriteUp