基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

PostgreSQL 的数据行(Tuple)头部信息中,xmin、xmax、cmin 和 cmax 分别代表什么?

知识点图片

在 PostgreSQL 中,xminxmaxcmincmax 是数据行(Tuple/元组)头部信息(Tuple Header)中极其重要的四个系统字段。它们是 PostgreSQL 实现 MVCC(多版本并发控制) 的核心基石。

PostgreSQL 在更新数据时,并不会直接覆盖原有的数据,而是插入一条新数据,并标记老数据被“删除”。这四个字段就是用来记录和追踪这些数据版本的“生命周期”的。

以下是它们的详细解释:

1. xmin (Insert Transaction ID)

  • 含义: 插入事务的 ID。它记录了是哪一个事务(Transaction ID,简称 XID)创建(插入)了这条数据行。
  • 触发时机: 当执行 INSERT 或者 UPDATE 操作时,新产生的数据行会被打上当前事务的 ID 作为 xmin
  • 作用: 它标志着这条数据的 “诞生”。只有当系统的当前事务 ID 大于或等于 xmin,且 xmin 对应的事务已经提交时,这条数据对当前事务才是可见的。

2. xmax (Delete Transaction ID)

  • 含义: 删除/更新事务的 ID,或者行锁事务 ID。它记录了是哪一个事务删除了这条数据行。
  • 触发时机:
    • 默认情况下,新插入的数据 xmax 为 0(表示未被删除)。
    • 当执行 DELETE 操作时,该行的 xmax 会被设置为执行删除的事务 ID。
    • 当执行 UPDATE 操作时,旧数据行的 xmax 会被设置为当前事务 ID(同时新插入一行,其 xmin 为当前事务 ID)。
    • 当执行 SELECT ... FOR UPDATE(行级锁)时,xmax 也会被记录为加锁的事务 ID。
  • 作用: 它标志着这条数据的 “死亡”。如果一条数据的 xmax 不为 0,且对应的事务已经提交,那么这条数据就被视为已删除,对后续的事务不可见。

3. cmin (Command ID for Insert)

  • 含义: 插入命令 ID。它记录了在 xmin 所在的事务内部,是第几个 SQL 命令插入了这条数据。
  • 触发时机:xmin 同步产生。事务内的第一个命令 ID 为 0,第二个为 1,依此类推。
  • 作用: 用于同一个事务内部的可见性判断。例如,在一个事务中,你先执行了 INSERT,然后紧接着在这个事务里执行 SELECTSELECT 之所以能看到刚刚 INSERT 的数据,就是通过比对当前命令 ID 和数据的 cmin 来实现的。

4. cmax (Command ID for Delete)

  • 含义: 删除命令 ID。它记录了在 xmax 所在的事务内部,是第几个 SQL 命令删除或更新了这条数据。
  • 触发时机: 当数据被 DELETEUPDATE 时产生。
  • 作用: 同样用于同一个事务内部的可见性判断。如果在一个事务中删除了某行,该事务后续的 SELECT 命令就不应该再看到这行数据,这就是通过比对 cmax 实现的。

💡 底层优化注意(硬核知识):
为了节省存储空间,在 PostgreSQL 的物理存储结构(t_tinfo 结构体)中,cmincmax 实际上共用同一个 32 位的物理字段(称为 t_cid
因为在绝大多数情况下,一个事务不可能既插入了一条数据,又在同一个事务里删除了它(如果真的发生了,PostgreSQL 会在内存中生成一个叫 Combo CID 的映射表来解决冲突),所以它们交替使用同一个物理空间是安全的。


📌 一个完整的生命周期示例

假设有一张表 test_table,我们通过三个事务来看看这些值的变化:

事务 100:插入一条数据

sql
BEGIN; -- 假设分配的事务ID为 100
INSERT INTO test_table (id, val) VALUES (1, 'A'); -- 命令ID为 0
COMMIT;
  • 此时底层数据行(Tuple 1): xmin = 100, xmax = 0, cmin = 0

事务 101:更新这条数据

sql
BEGIN; -- 假设分配的事务ID为 101
UPDATE test_table SET val = 'B' WHERE id = 1; -- 命令ID为 0
COMMIT;
  • 此时底层会有两条数据行:
    • 旧行(Tuple 1): xmin = 100, xmax = 101, cmin = 0, cmax = 0 (已标记死亡)
    • 新行(Tuple 2): xmin = 101, xmax = 0, cmin = 0 (新诞生)

事务 102:删除这条数据

sql
BEGIN; -- 假设分配的事务ID为 102
DELETE FROM test_table WHERE id = 1; -- 命令ID为 0
COMMIT;
  • 此时底层数据行(Tuple 2): xmin = 101, xmax = 102, cmin = 0, cmax = 0 (已标记死亡)
  • (最终,这些被标记为死亡的旧元组(Tuple 1 和 Tuple 2),会在 PostgreSQL 的 VACUUM 后台进程运行时被真正的物理清理掉。)

🔍 如何在 SQL 中查看它们?

你可以直接在普通的 SELECT 语句中查询这些隐藏的系统列:

sql
SELECT xmin, xmax, cmin, cmax, * FROM test_table;
00:00
00:00