它们是 PostgreSQL 中最常被查询的五个系统列。它们都是隐式定义的(无需手动声明),始终存在(除了 tableoid 在某些分区情况下),并且 不会 被 SELECT * 返回 ------ 必须显式指定列名。
它们实现了 MVCC(多版本并发控制)、行版本管理、可见性规则和物理行寻址。
快速汇总表
| 列名 | 类型 | 大小 | 用途 / 含义 | 典型值 / 备注 |
|---|---|---|---|---|
| xmin | xid | 4 字节 | 创建/插入此版本行的事务 ID | 行的"诞生"事务。对于大多数快照,其可见性始于该事务提交之时。MVCC 的核心。 |
| xmax | xid | 4 字节 | 删除/更新(使失效)此行版本的事务 ID | 0 = 行仍存活(尚未被删除或更新)。非 0 = 行已死或被 UPDATE 取代(新版本已存在)。可能暂时持有多事务锁 ID。 |
| cmin | cid | 4 字节 | 在插入事务内的命令 ID | 事务内的哪个语句/命令创建了这个版本。对于简单事务通常为 0。用于 事务内部可见性。 |
| cmax | cid | 4 字节 | 在删除事务内的命令 ID,如果未删除则为 0 | 事务内的哪个语句使此行失效。对存活的行则为 0。cmin/cmax 在元组头中共用同一个存储槽位(重叠)。 |
| ctid | tid | 6 字节 | 此行版本的物理位置:(块号, 块内元组索引) | "行指针"。UPDATE 或 VACUUM FULL 时会改变。用于 HOT 链(在同一页面上指向新版本的前向指针)。可用作物理定位器(但不稳定)。 |
可见性规则(MVCC 的核心)
PostgreSQL 根据 快照 结合这些字段,决定一个行版本对你的 事务 是否可见:
行版本可见的条件(简化版,基于"读已提交"/"可重复读"规则):
-
xmin已提交 且xmin≤ 快照的水平线 -
并且
xmax = 0或
xmax正在进行中 或
xmax> 快照的水平线 或
xmax已中止/回滚
事务内部可见性 (同一事务能看到自身的更改)还会用到 cmin/cmax 和命令序列。
-
在 读已提交 级别:每个语句获取新快照 → 能在事务中途看到其他并发提交的更改。
-
在 可重复读 / 可序列化 级别:在第一个语句处获取快照 → 在整个事务期间保持稳定的视图。
实践示例
-- 查看它们(它们对 SELECT * 隐藏)
SELECT xmin, xmax, cmin, cmax, ctid, * FROM mytable WHERE id = 42;
-- 典型的存活行(刚插入,尚未更新/删除)
xmin | xmax | cmin | cmax | ctid
-----+------+------+------+---------
742 | 0 | 0 | 0 | (123,7)
-- 执行 UPDATE 后的行(旧版本,xmax 被更新事务设置)
xmin | xmax | cmin | cmax | ctid
-----+------+------+------+---------
742 | 745 | 0 | 1 | (123,7) ← 旧版本,xmax = 745
-- UPDATE 创建的新版本
xmin | xmax | cmin | cmax | ctid
-----+------+------+------+---------
745 | 0 | 1 | 0 | (124,3) ← 新版本,ctid 通常不同
重要行为
-
UPDATE → 创建新的行版本(新的
xmin,ctid通常改变);旧版本被设置xmax→ 更新者提交后变为死行。 -
HOT UPDATE(堆内元组) → 更新发生在同一页面,索引不变 → 旧的
ctid更新为指向新位置 → 避免索引膨胀。 -
DELETE → 设置
xmax,不会创建新版本。 -
VACUUM → 清理死行版本(即
xmax已提交且小于最老运行事务的行)。 -
ctid不稳定 ------ 切勿将其用作永久标识符(请用主键)。可用于调试重复行、追踪 HOT 链或底层页面检查。
底层访问(pageinspect 扩展)
CREATE EXTENSION pageinspect;
-- 查看原始元组头字段 (t_xmin ≈ xmin, 等)
SELECT lp, t_xmin AS xmin, t_xmax AS xmax, t_cid AS cid, t_ctid AS ctid
FROM heap_page_items(get_raw_page('mytable', 0));
总结
-
xmin / xmax → 行的诞生与消亡(MVCC 的核心机制)
-
cmin / cmax → 多语句事务内部命令的先后顺序(除深度调试外很少需要)
-
ctid → 物理位置(经常变化,对内部机制分析和取证有用)
正是这些字段,使得 PostgreSQL 能够在 不加读锁 的情况下提供出色的并发能力 ------ 每个事务都能看到自己的一致数据版本。