xid是uint32类型的,GetNewTransactionId函数中xid在ShmemVariableCache->nextXid中取值,但是ShmemVariableCache->nextXid是long int类型的。
unsigned int : 0 ~ 4294967295 ( 0 ~ 2^32-1 )
int : -2147483648 ~ 2147483647 ( -2^31 ~ 2^31-1 )
long int : -9223372036854775808 ~ 9223372036854775807 ( -2^63 ~ 2^63-1 )
GetNewTransactionId
FullTransactionId full_xid;
TransactionId xid;
full_xid = ShmemVariableCache->nextXid;
xid = XidFromFullTransactionId(full_xid);
所以xid的取值会从0到4294967295在归零再次到4294967295不停循环。而ShmemVariableCache->nextXid是一直上涨的,因为ShmemVariableCache->nextXid的范围是( -2^63 ~ 2^63-1 )。
注意ShmemVariableCache->nextXid到正数最大值9223372036854775807后在加一会溢出到负数最小值-9223372036854775808,这时强转到uint32时为0,正好又是一轮循环。
取值规律见下面实例:
ShmemVariableCache->nextXid
nextXid: 0 1 2 3 ... 4294967295 4294967296 4294967297 4294967298 ... 9223372036854775807
xid : 0 1 2 3 ... 4294967295 0 1 2 ... 4294967295
ShmemVariableCache->nextXid
nextXid: 9223372036854775807 -9223372036854775808 -9223372036854775807 ... 0
xid : 4294967295 0 1 0
ShmemVariableCache->nextXid自加使用FullTransactionIdAdvance函数
static inline void
FullTransactionIdAdvance(FullTransactionId *dest)
{
dest->value++;
/* see FullTransactionIdAdvance() */
if (FullTransactionIdPrecedes(*dest, FirstNormalFullTransactionId))
return;
while (XidFromFullTransactionId(*dest) < FirstNormalTransactionId)
dest->value++;
}
已TransactionIdFollows函数为例,入参是两个uint32(0 ~ 2^32-1)。
但是做减法的时候结果保存到diff是int32(-2^31 ~ 2^31-1)。
typedef uint32 TransactionId;
/*
* 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);
}
(id1 - id2)
不管怎么减结果都是正数,因为这是两个uint32在减。这个正数结果代表这两个值的距离。当前距离-5
id1 = 4294967290u
id2 = 4294967295u
id1 - id2 = 4294967291u
diff = (int32)(id1 - id2) = -5
id2继续增长,但是id2是uint32最大值就是4294967295u了,继续增长后溢出
id1 = 4294967290u
id2 = 10u
id1 - id2 = 4294967280u
diff = (int32)(id1 - id2) = -16
可以看到溢出后,结果仍然是负数,TransactionIdPrecedes函数的计算是正确的id1 logically < id2
。
**但是如果id1和id2距离过大,超过231后,例如id2从刚才的10继续增长到2147483647**,id2领先id1的距离已经超过了231:
id1 = 4294967290u
id2 = 2147483647u
id1 - id2 = 2147483643u
diff = (int32)(id1 - id2) = 2147483643
结果diff又翻转了一次变成了正数,虽然id1逻辑上应该<id2,但是这时TransactionIdPrecedes的结果已经是false了。
显然TransactionIdPrecedes函数计算错误。
所以在PG现有的xid分配机制上,为了保证xid回卷后还能正确的对比大小,两个xid的距离不能超过2^31。
方便记忆:事务ID可以回卷,但最老的到最新的距离不能超过20亿,否则会发生第二次符号翻转,事务ID计算结果全部都会出错。