黄金外汇网站建设,深圳公司排名100强,浙江大学微纳加工平台,吉林建设厅官方网站事务ID#xff08;XID#xff09;基本概念
从Transactions and Identifiers可知#xff1a; 事务 ID#xff0c;例如 278394#xff0c;会根据 PostgreSQL 集群内所有数据库使用的全局计数器按顺序分配给事务。此分配会在事务首次写入数据库时进行。这意味着编号较低的 x…事务IDXID基本概念
从Transactions and Identifiers可知 事务 ID例如 278394会根据 PostgreSQL 集群内所有数据库使用的全局计数器按顺序分配给事务。此分配会在事务首次写入数据库时进行。这意味着编号较低的 xid 会先于编号较高的 xid 写入。 事务 ID 类型 xid 为 32 位宽每 40 亿次事务绕回一次。每次绕回时都会增加一个 32 位的纪元 (epoch)。此外还有一个 64 位类型 xid8它包含这个纪元因此在安装的生命周期内不会绕回它可以通过强制类型转换转换为 xid。xid 是 PostgreSQL MVCC 并发机制和流复制的基础。 交易ID和快照信息函数参见这里。
日常清理VACUUM中的冻结操作
PostgreSQL 数据库需要定期维护称为清理vacuum。其中除更新统计信息回收空间外一项重要的任务就是防止由于事务 ID 回绕或多事务 ID 回绕而丢失非常旧的数据。
从Preventing Transaction ID Wraparound Failures可知 PostgreSQL 的 MVCC 事务语义依赖于能够比较事务 ID (XID) 编号如果行版本的插入 XID 大于当前事务的 XID则该行版本“位于未来”对当前事务不可见。但由于事务 ID 的大小有限32 位因此长期运行的集群超过 40 亿个事务将遭遇事务 ID 回绕XID 计数器会回绕为零过去的事务会突然变成未来的事务 — — 这意味着它们的输出变得不可见。简而言之就是灾难性的数据丢失。实际上数据仍然存在但如果您无法获取数据这也只是些安慰。为了避免这种情况有必要至少每 20 亿个事务清理一次每个数据库中的每个表。 为何是至少每 20 亿个事务清理一次这是由autovacuum_freeze_max_age参数控制的
sampledb show autovacuum_freeze_max_age;autovacuum_freeze_max_age
---------------------------200000000
(1 row)20 亿实际是XID取值范围的一半即231。 定期清理能够解决这个问题的原因是VACUUM 会将行标记为冻结表明这些行是由一个提交时间足够久的事务插入的因此插入事务的影响对所有当前和未来的事务都可见。普通 XID 使用模 232 算法进行比较。这意味着对于每个普通 XID都有 20 亿个“更旧”的 XID 和 20 亿个“更新”的 XID换句话说普通 XID 空间是循环的没有端点。因此一旦使用特定的普通 XID 创建了行版本那么在接下来的 20 亿个事务中无论我们讨论的是哪个普通 XID该行版本都会看起来像是“过去”的。如果在超过 20 亿个事务之后该行版本仍然存在它就会突然看起来像是未来。为了防止这种情况PostgreSQL 保留了一个特殊的 XIDFrozenTransactionId它不遵循普通 XID 比较规则并且始终被认为比所有普通 XID 都旧。冻结行版本被视为插入 XID 是 FrozenTransactionId因此无论环绕问题如何它们对于所有正常事务都将显示为“过去”因此此类行版本将一直有效直到被删除无论时间有多长。 所谓是循环的没有端点类似于下图
0 → 1 → 2 → ... → 2^32-1 → 0 → 1 → ...每一个表都有系统定义的隐含列xmin和xmax
sampledb select xmin, xmax from regions limit 1;xmin | xmax
------------5190 | 0
(1 row)所谓冻结就是将xmin的值设为FrozenTransactionId实际值为2设置后xmin的值不会再被修改。 vacuum_freeze_min_age 控制 XID 值的有效期超过该 XID 值的行才会被冻结。如果原本会被冻结的行很快会被再次修改则增加此设置可以避免不必要的工作但降低此设置会增加在必须再次清理表之前可以处理的事务数。 表未清理的最长时间是20亿个事务数减去上次激进清理时的vacuum_freeze_min_age值。如果未清理的时间超过该时间可能会导致数据丢失。为确保不会发生这种情况任何可能包含XID大于配置参数autovacuum_freeze_max_age指定的未冻结行的表都会被调用自动清理。即使禁用自动清理也会发生这种情况。 事务ID环绕问题是如何产生和解决的
前面已经谈到了普通 XID 使用模 232 算法进行比较。这个规则就是
如果(NextXID - xmin) % 2^32 2^31则 xmin 属于过去PostgreSQL 将 “当前 XID ± 2^31 (≈ 20 亿)” 作为 可见窗口因此总有约20亿属于过去20亿属于未来中间那个|即NextXID
---------------- 2^31 2,147,483,648 ----------------“过去” “未来”
-----------------------|------------------------------可见 不可见先看一个属于过去的例子。
假设xmin 4,294,967,000接近2^32。XID已经回绕此时NextXID 100。 根据算法(NextXID - xmin) % 2^32 2^31。
NextXID 100
xmin 4,294,967,000
delta (100 - 4,294,967,000) % 4,294,967,296 ( -4,294,966,900 ) % 4,294,967,296 396显然396小于2^31因此xmin虽然接近XID的最大值但属于过去。
再看一个属于未来的例子xmin和上例相同
NextXID 2,147,483,700
xmin 4,294,967,000
delta (2,147,483,700 - 4,294,967,000) % 4,294,967,296 (-2,147,483,300) % 4,294,967,296 2,147,483,996此时delta大于2^31因此xmin属于未来。
xmin并没有发生变化而此时却被视为未来这显然是错误的。通过冻结即将xmin置为FrozenTransactionId即可解决事务ID环绕问题。
在源码文件./backend/access/transam/transam.c中可以找到此算法
// git clone https://github.com/postgres/postgres.git 检出源码
/** TransactionIdPrecedes --- is id1 logically id2?*/
bool
TransactionIdPrecedes(TransactionId id1, TransactionId id2)
{/** If either ID is a permanent XID then we can just do unsigned* comparison. If both are normal, do a modulo-2^32 comparison.*/int32 diff;if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))return (id1 id2);diff (int32) (id1 - id2);return (diff 0);
}监控XID环绕
postgres# SELECT datname,age(datfrozenxid) AS xid_age,2000000000 - age(datfrozenxid) AS remaining_before_wraparound
FROM pg_database;datname | xid_age | remaining_before_wraparound
----------------------------------------------------------postgres | 5375 | 1999994625template1 | 5375 | 1999994625template0 | 5375 | 1999994625world_temperatures | 5375 | 1999994625demo | 5375 | 1999994625sampledb | 5375 | 1999994625
(6 rows)可以参照Transaction ID wraparound: a walk on the wild side模拟事务ID环绕问题。