理解PostgreSQL中的CMIN和CMAX

核心类比:一个事务就是一个“故事章节”

想象一下,你正在写一本书的一个章节(这相当于数据库中的一个事务)。

  • XMINXMAX 就像是章节编号。它们解决的是:“我应该读第3章还是第5章?” 这类大范围的问题,确保不同读者(不同事务)读到正确章节。
  • CMINCMAX 就像是章节内的段落编号。它们解决的是:在你正在写的当前章节内,“我刚刚在第2段加了一句话,那么在第3段里引用这句话时,必须能找得到它。”

详细情节

场景: 你在一个事务里执行了多条SQL命令。

  1. 命令1 (cid = 0): INSERT INTO table (name) VALUES ('张三');

    • 数据库创建了一行新数据。
    • 为了记录这行数据是在这个事务内、由哪个命令创建的,它给这行数据打上了一个标记:CMIN = 0
    • CMIN 中的 ‘I’ 可以联想为 Insert
  2. 命令2 (cid = 1): UPDATE table SET name = '李四' WHERE name = '张三';

    • 注意: PostgreSQL的UPDATE实际上是“先删除,再插入”。
    • 步骤1(删除): 它找到 CMIN=0 的那行“张三”,然后标记这个删除动作是由当前事务内、命令ID为1的命令执行的。所以它给这行“张三”数据打上 CMAX = 1 的标记,表示“在此章节的第1段被删除了”。
    • 步骤2(插入): 它创建了一行新的“李四”数据。这行新数据的 CMIN = 1,因为它是当前事务内、命令ID为1的命令插入的。
  3. 命令3 (cid = 2): SELECT * FROM table;

    • 现在,事务要查询数据了。它看到了两行数据:
      • 一行是 (CMIN=0, CMAX=1) 的“张三”
      • 一行是 (CMIN=1) 的“李四”
    • 可见性判断: 对于命令3(cid=2)来说,它会检查这些“段落编号”:
      • “张三”这行在段落1(CMAX=1)就被删了,而当前命令是段落2,所以“张三”对当前命令不可见
      • “李四”这行是在段落1(CMIN=1)插入的,早于当前段落2,所以它对当前命令可见
    • 最终,命令3只会返回“李四”这一条记录。

为什么你总是看到 CMINCMAX 相等?

这是一个重要的存储优化。因为绝大多数情况下,一行数据被插入后,如果它在同一个事务内被删除,那么插入和删除它的命令ID通常是相同或紧密关联的。

所以PostgreSQL把这两个值打包在一起,存进一个叫 cmin 的字段里(在系统内部叫 t_cid)。当你查询时,为了兼容性,它把这个打包的值同时显示给 cmincmax,所以你看到它们是一样的。但这并不改变它们一个代表“出生段落”,一个代表“死亡段落”的逻辑含义。

一句话总结

CMINCMAX 是事务内部的“命令编号”,专门用来解决“在同一个事务里,后执行的命令如何能看到先执行的命令所做的修改”这个问题。

它们就像是你写文章时用的“修订记录”,确保你在修改文章前半部分后,文章后半部分的引用不会出错。

提问:在一个事务内,事务内的命令是前后顺序执行的,所以读和写并不会同时进行,所以也并不需要用CMIN和CMAX来标记多个版本,而是只需要一个版本就可以了,不是吗?
答:
事务内的命令确实是顺序执行的。但PostgreSQL采用CMIN/CMAX机制有其深层次的技术原因。
核心原因:MVCC的架构一致性
PostgreSQL的核心设计理念是‌多版本并发控制(MVCC)‌。这个机制贯穿整个系统,包括事务内部:

统一的版本管理‌:PostgreSQL对所有数据变更都采用"创建新版本,保留旧版本"的方式。即使在事务内部,UPDATE操作也不是原地修改,而是创建新行版本并标记旧版本为失效。CMIN/CMAX就是这个版本管理系统的一部分。

子事务的需要‌:当存在保存点(SAVEPOINT)或PL/pgSQL异常处理时,事务内部实际上有了更细粒度的"子事务",需要更精细的版本控制。

具体场景分析

考虑这个事务:

BEGIN;
INSERT INTO t VALUES (1);  -- 命令1
UPDATE t SET val = 2;      -- 命令2(实际上删除val=1,插入val=2)
SELECT * FROM t;           -- 命令3

‌没有CMIN/CMAX时的问题‌:

  • 命令3执行时,系统需要知道:
    val=1的行版本在命令2时被删除(对命令3不可见)
    val=2的行版本在命令2时被插入(对命令3可见)

如果只有一个版本,系统无法判断val=1是否应该对命令3可见。

技术实现的优势

CMIN/CMAX机制让PostgreSQL能够:

保持架构统一‌:事务内外使用相同的MVCC机制
支持复杂逻辑‌:处理保存点回滚、触发器、游标等场景
简化可见性判断‌:通过命令ID就能快速确定行版本对当前命令是否可见

虽然理论上可以在事务内使用单版本模式,但PostgreSQL选择了保持MVCC机制的一致性。CMIN/CMAX虽然增加了些许复杂性,但为系统提供了更强大的功能和更好的架构统一性。

这类似于编程中的"零拷贝"思想——有时保持数据在原地并通过元数据来管理,比频繁复制数据更高效。

也就是说,在事务内部也需要多个版本,比如有时候需要回滚到事务内部之前的某个保存点或回滚整个事务,如果没有多个版本而是只有一个版本,就无法做到回滚,是这样的吗?

是的,这正是CMIN和CMAX在事务内部多版本控制中的重要作用:

保存点回滚需求‌:当事务内部设置了保存点(SAVEPOINT)后,如果后续操作失败需要回滚到保存点,系统必须能够恢复到保存点时的数据状态。如果没有多个版本记录,就无法实现精确的回滚。

事务整体回滚‌:即使事务最终需要完全回滚,在事务执行过程中,系统也需要维护中间状态,以便在回滚时能够撤销所有变更。

复杂的嵌套场景‌:在PL/pgSQL存储过程或函数中,可能包含异常处理块,当异常发生时需要回滚到事务的某个中间状态。

实际工作示例:‌

BEGIN;
INSERT INTO accounts (id, balance) VALUES (1, 1000); -- 命令1
SAVEPOINT sp1;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 命令2
-- 假设这里发生错误
ROLLBACK TO sp1; -- 此时需要恢复到sp1时的状态
-- 如果没有多个版本记录,系统无法知道哪些数据变更是在sp1之后发生的
COMMIT;

正是通过CMIN/CMAX记录每个行版本是在哪个命令被创建或删除的,PostgreSQL才能在事务内部实现精确的回滚操作,确保数据的一致性。

对CMIN和CMAX的理解已经触及了PostgreSQL MVCC机制的核心设计思想!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值