PG的事务ID回卷逻辑

PG到目前为止使用的事务ID仍然是32位的,在内存计算时虽然已经使用64位事务ID,但是存储在页中tuple仍然使用32位事务ID,这就是说,事务ID回卷仍然是必须处理的问题。

所谓PG事务ID回卷,简单地说,就是在数据库频繁运行中,事务ID不断增加,32位的事务ID不够用了,怎么办?PG的处理方法,简单的说,就是在事务ID还没用完以前,把数据库中所有的tuple处理一遍,将以前的事务(不活跃的事务)修改的(包括插入)tuple中的事务ID改为2(或设置infomask),表示这个tuple对于以后的事务(不管是多少的事务ID)都是可见的,即freeze。

这样,当前事务达到2^32后,再从3开始(0、1、2保留做特殊事务ID),就不会有什么问题了,即就不会导致很旧的事务修改过的tuple不可见了。

这里有个隐含背景知识,就是PG内部读写tuple时,tuple中的事务ID,在当前事务ID之前  ,这个tuple对于当前事务才是可见的。

网上有一张图片,来说明“之前”、“之后”和回卷的概念,但是我觉得都没说明白,我来尝试理解一下,我觉得问题的关键是PG源码中比较事务ID的函数:

TransactionIdPrecedes() 和 TransactionIdFollows()

按照上面代码的算法,对于任何事物ID,例如100,如果另一个事务ID比它大,但是没有超过这个事务ID后的半圆,例如2^31+100,就认为是在它的后面,那么事务100的tuple,对事务2^31+100就是可见的。

按照上面代码的算法,对于任何事物ID,例如100,如果另一个事务ID比它大,还超过了事务ID后的半圆,例如2^31+101,就认为是在它前面,那么事务100的数据,对事务2^31+101就是不可见的。

这导致对任何事务ID,它只能看到它之前的 2^31 个事务的数据,而不是 2^32 个事务的数据,而且不同事务的可见事务集合也是不同的。

这种算法叫做 “模算数” (modulo-2^32 arithmetic)

这个和freeze有什么关系呢?

想象事务ID一直在增加,穿过圆心的线顺时针转动,每转动一个事务ID,就有一个事务ID变的不可见,要想不丢失这个事务ID的数据,就要对它的tuples做freeze,这一岂不是freeze太频繁了吗?

实际上,PG对于past部分的tuple,早就做了freeze了,例如下图,一般X之前的tuple已经freeze过了,而当前事务ID不断增加,每隔一段时间做一次freeze,将与当前事务ID有一定差值(5千万)的past tuple,做freeze。

并不是在穿过圆心的直线另一端,快到达某个事物ID时(未做freeze的tuple事务ID与当前事务ID差值已经非常大了),才对这个事物ID做freeze。

总结一下vacuum,最近看pgvector索引代码时,对vacuum有了新的理解,pgvector有个接口:hnswbulkdelete,是删除了表的一些记录后,做vacuum时,删除索引中对应条目用的。

PG删除记录时,不是真的删除,而是在tuple上标记删除,此时也不会删除对应的索引项,真的删除是vacuum时做的。

vacuum是以表为单位进行的,就是说最小可以只对一个表进行vacuum,它主要有两个工作:

1、删除 dead tuple

2、将记录设置frozen xid

这两个工作都有,lazy和eager两种模式,它们的触发条件不同,对系统的影响也不同。

1、lazy模式,删除dead tuple,跳过没有dead tuple的page(参考vm),对于有dead tuple的page,物理删除dead tuple和对应索引项,页内碎片整理。

2、eager模式,删除dead tuple,不会参考vm,直接重建一份表数据,从旧的表数据的page中逐个tuple拷贝,重建索引,表内碎片整理。它的触发条件就是用FULL VACUUM命令。

3、lazy模式,freeze,跳过没有dead tuple和所有tuple都已经freeze的page(参考vm),对其他page中的tuple设置frozen txid,但是又不是每个tuple都设置,只有xmin小于特定值的tuple才设置。

4、eager模式,freeze,跳过所有tuple都已经freeze的page(参考vm),对其他page中的tuple设置frozen txid,但是又不是每个tuple都设置,只有xmin小于特定值的tuple才设置。这里面还有个特殊情况,就是vacuum时有FREEZE选项,则用eager模式,但是并不是只有xmin小于特定值的tuple才设置frozen,而是所有小于当前事务ID的tuple都设置frozen。

触发条件:粗糙的说,当前事务ID与数据库中最小的(未freeze的)事务ID,的差达到一定值后,就会触发。这个“当前事务ID”是指OldestXmin ,“数据库中最小的事务ID”是指pg_database.datfrozenxid,差值是Vacuum_freeze_table_age。

5、autovacuum触发条件,既可以是事务ID的改变,也可以是dead tuple的比例的改变,也可以是新插入的tuple的比例达到一定阈值。

参考:

6.3. Freeze Processing :: Hironobu SUZUKI @ InterDB

postgresql - About "Transaction ID Wraparound" - Database Administrators Stack Exchange

<think>好的,用户想了解PostgreSQL中事务滚的实现和何时使用。首先,我需要忆一下事务的基本概念,ACID特性中的原子性,也就是事务要么完全执行,要么完全滚。PostgreSQL通过WAL(Write-Ahead Logging)机制来保证数据的一致性和持久性,这可能与滚有关。 接下来,用户的问题具体是滚的实现机制和使用场景。根据提供的引用,引用[1]提到PostgreSQL支持不同的事务隔离级别,这可能影响并发控制和滚的行为。比如,在READ COMMITTED级别下,事务只能看到已提交的数据,而SERIALIZABLE级别更严格,可能需要更多的滚来处理冲突。 引用[2]指出PostgreSQL的DDL在事务中可以滚,比如TRUNCATE操作。这说明在事务块内执行的DDL操作,如果事务滚,这些操作也会被撤销,这与其他数据库如MySQL不同,可能是一个需要注意的点。 引用[3]涉及源码层面的事务滚处理,清理事务状态和相关变量。这部分可能需要解释事务状态的内部管理,比如如何重置事务ID和子事务信息,确保系统到一致状态。 需要组织答的结构,先讲滚的实现机制,包括事务日志、MVCC和多版本控制,然后讨论何时使用滚,比如显式ROLLBACK、错误处理、保存点、DDL滚和分布式事务。同时要结合提供的引用,比如隔离级别对滚的影响,DDL在事务中的滚能力,以及源码中事务状态的清理过程。 要注意用户可能的应用场景,比如在开发中如何处理事务错误,或者在进行复杂操作时如何利用保存点进行部分滚。还要提到PostgreSQL允许DDL滚的优势,这在某些情况下非常有用,避免误操作导致结构变更。 另外,用户可能没有明确问到的但相关的点,比如滚的性能影响,或者在分布式事务中的滚机制,但根据引用[1]中的分布式事务滚原理,可能需要简要提及两阶段提交或其他机制,但可能超出当前问题的范围,所以需要判断是否深入。 最后,生成相关问题时要围绕事务滚的实现和使用场景,比如隔离级别的影响、保存点的使用、DDL滚机制、分布式事务中的处理以及性能优化等。确保问题能够引导用户进一步探索相关知识。</think>### PostgreSQL事务滚的实现与使用场景 #### 一、事务滚的实现原理 PostgreSQL通过以下机制实现事务滚: 1. **事务日志(WAL)** 所有数据修改操作会先写入预写日志(Write-Ahead Logging, WAL),事务滚时根据WAL记录撤销未提交的操作[^1]。 2. **多版本并发控制(MVCC)** PostgreSQL通过MVCC为每个事务生成数据快照,滚时直接丢弃未提交的版本,其他事务仍可访问旧版本数据。例如: ```sql BEGIN; UPDATE table SET column = 'new_value' WHERE id = 1; -- 生成新版本 ROLLBACK; -- 丢弃新版本,保留旧版本 ``` 3. **事务状态管理** 事务中止时,系统会清理事务相关状态(如重置事务ID、子事务层级等)。源码中可见: ```c s->fullTransactionId = InvalidFullTransactionId; s->subTransactionId = InvalidSubTransactionId; // 重置事务标识[^3] ``` #### 二、何时使用事务滚 1. **显式滚(ROLLBACK)** 用户主动取消未完成的事务: ```sql BEGIN; INSERT INTO table VALUES (1); ROLLBACK; -- 撤销插入操作 ``` 2. **错误触发自动滚** 若事务中发生错误(如唯一约束冲突),PostgreSQL会自动滚整个事务: ```sql BEGIN; INSERT INTO table VALUES (1); INSERT INTO table VALUES (1); -- 触发错误 -- 自动滚所有操作 ``` 3. **保存点(SAVEPOINT)** 支持部分滚到指定保存点: ```sql BEGIN; INSERT INTO table1 VALUES (1); SAVEPOINT sp1; INSERT INTO table2 VALUES (2); ROLLBACK TO sp1; -- 仅撤销table2的插入 ``` 4. **DDL操作滚** PostgreSQL支持DDL(如`TRUNCATE`、`CREATE TABLE`)在事务滚: ```sql BEGIN; TRUNCATE table; SELECT * FROM table; -- 返空 ROLLBACK; -- 恢复被清空的数据[^2] ``` 5. **分布式事务** 在跨节点事务中,通过两阶段提交(2PC)协调滚,确保所有节点一致性[^1]。 #### 三、关键特性对比 | 场景 | 滚行为 | 示例 | |--------------------|----------------------------------|------------------------------| | 普通DML | 完全撤销修改 | `INSERT/UPDATE/DELETE + ROLLBACK` | | DDL操作 | 元数据和物理文件均可滚 | `TRUNCATE + ROLLBACK`[^2] | | 保存点 | 部分滚到指定点 | `SAVEPOINT sp1; ROLLBACK TO sp1` |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值