当我们开始接触PostgreSQL的时候,我们会了解到PostgreSQL事务id是通过32位无符号数来表示的:2^32 = 4294967296,当事务id超过40亿以后就会有溢出的风险,为了防止事务回卷,PostgreSQL通过vacuum freeze的方式来循环利用这些事务号,如果vacuum freeze 不及时或者遇到故障,数据库会给相关报警提示,甚至关闭数据库,可以参考以下链接模拟复现:https://www.modb.pro/db/31736,之所以没有引入64位无符号数来解决这个问题,是因为存储64位的元组头部信息(xmin,xmax)需要扩大存储的字节(2bit),而社区希望通过借助缓存的方法,只使用一个bit就能达到64位的效果。
事务号比较
/*
* 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);
}
事务号是通过(int32) (id1 - id2)来对比的,当事务号发生了回卷,即id2的值比id1的值小,但是由于(id1 - id2)大于2^31, 经过int32后转换成一个负数,但如果id1 和 id2 都是回卷前的数值且大于2^31, 同样会出现问题,所以PostgreSQL要保证两个有效的事务id不能大于2^31。
xid与txid 查询转换
再运维过程中我们会关注数据库当前的事务号,需要通过txid_current()函数来查看,我们会发现当前的事务号已经远远大于2^32,如图所示
这里我们注意到,我们查询事务id用的是txid而不是xid,这是因为txid是在全局的视角上做的持久化,可以被其他机器使用,txid是不会被循环的64位整形数值,而xid是一个32位的类型,xmin和xmax都是xid类型,xid类型的数值是会被freeze循环使用的,只有最顶层的xid才会通过epoch来实现txid和xid转换。通过xid类型的数值可以得到事务的结束时间,而通过txid可以得到事务的状态。
/*
* Export internal transaction IDs to user level.
*
* Note that only top-level transaction IDs are ever converted to TXID.
* This is important because TXIDs frequently persist beyond the global
* xmin horizon, or may even be shipped to other machines, so we cannot
* rely on being able to correlate subtransaction IDs with their parents
* via functions such as SubTransGetTopmostTransaction().
*
/* txid will be signed int8 in database, so must limit to 63 bits */
#define MAX_TXID UINT64CONST(0x7FFFFFFFFFFFFFFF)
/*
* do a TransactionId -> txid conversion for an XID near the given epoch
*/
static txid
convert_xid(TransactionId xid, const TxidEpoch *state)
{
uint64 epoch;
/* return special xid's as-is */
if (!TransactionIdIsNormal(xid))
return (txid) xid;
/* xid can be on either side when near wrap-around */
epoch = (uint64) state->epoch;
if (xid > state->last_xid &&
TransactionIdPrecedes(xid, state->last_xid))
epoch--;
else if (xid < state->last_xid &&
TransactionIdFollows(xid, state->last_xid))
epoch++;
return (epoch << 32) | xid;
}
转换公式可以简化成:txid = xid + epoch * 2^32
epoch可以通过pg_controldata命令获取到,即NextXID中分号前面的数值
epoch 可以通过pg_resetwal -e 2 /data/pgdata14/ 的方式修改,修改epoch需要停止数据库实例。
xid的获取可以通过两种方式:
1、通过pg_waldump 解析wal日志
2、直接在表中查询: select xmin,xmax,* from tablename;
通过xid查事务结束时间及通过txid 查事务状态,这里以epoch=1为例
事务ID特性
1、普通的事务号是从3开始的,0表示无效的xid;1表示初始化时创建catalog的xid;2表示冻结xid。
2、两个有效的事务号之间相差不能大约2^31。
3、由于数据库中的事物号非常宝贵,对于未开启显示事务的查询来说,是不会消耗事务号的。