Freeze
PG需要为每个事务分配事务ID,这是MVCC能工作的基础,类似于一个时间戳的概念。
时间戳的做基本动作就是比较大小,即需要得到哪条事务在先,哪条在后。
首先我们来看一下PG中事务ID的比较逻辑,这部分在之前的某一起DB月报中也介绍过。
/*
* 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);
}
TransactionIdIsNormal
宏的作用是判断id1是否大于等于3。这里值得注意的是diff = (int32) (id1 - id2)
。这里使用了一个编程技巧,比如发生了事务ID回卷:
id1 = 4294967200
id2 = 100
// int32 -2147483648 ~ 2147483647 (32亿+)
// uint32 0 ~ 4294967295 (42亿+)
id1 - id2 = 4294967100
这个值强转成(int32)类型后是一个负数,会发生return true
,函数会认为id1 < id2
,也就是说id2是更新的事务。
我们继续考虑下一个场景,id2继续增长到21亿多时,id1和id2的差值已经在int32的范围内了,所以diff会大于零,return false
函数会认为id1= 42亿+ > id2= 21亿+
,结论是id1是更新的事务!这里就出现问题了,一个老事务被判断成了新事务。
所以PostgreSQL必须保证一个数据库中两个有效的事务之间的年龄差最多是,约为20亿。
1 Lazy Freeze
关注参数:
vacuum_freeze_min_age = 50000000(5千万)
lazy freeze是autovacuum和vacuum都会做的动作,所以vm在这里也是有效的,只有vm中标记的页面会做lazy freeze。
触发条件:
xmin小与freeze_txid的元组都会被freeze。
freeze_txid = current_oldest_xmin - vacuum_freeze_min_age
= 当前活跃的最小的xid - 参数vacuum_freeze_min_age
例如当前活跃的最小xid=50005100,vacuum_freeze_min_age为默认值50000000,那么freeze_txid=5100,也就是说xmin小于5100的元组都会被freeze(前提是所在的页面会被扫描到)
这里补充一个长事务的例子:
数据库运行一个长事务,很久没有提交导致current_oldest_xmin一直不会超过vacuum_freeze_min_age,vacuum不会冻结任何元组。这样最低的xmin就和当前最新的xmin的距离越来越远,差值慢慢接近20亿,这时候数据库为保证数据不丢失,会有告警甚至宕机。
告警
WARNING: database "mydb" must be vacuumed within 177009986 transactions
HINT: To avoid a database shutdown, execute a database-wide VACUUM in "mydb".
宕机
ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT: Stop the postmaster and vacuum that database in single-user mode.
2 Eager Freeze
关注参数:
vacuum_freeze_table_age = 150000000(1亿5千万)
触发条件:
pg_database.datfrozenxid < (OldestXmin−vacuum_freeze_table_age)
pg_database.datfrozenxid : 当前数据库被冻结的最大的事务ID,pg_database中可以查到
例如我们查询到当前的pg_database.datfrozenxid和vacuum_freeze_table_age的值为
select oid,datname,datfrozenxid from pg_database where oid=13214;
oid | datname | datfrozenxid
-------+----------+--------------
13214 | postgres | 548
show vacuum_freeze_table_age;
vacuum_freeze_table_age
-------------------------
150000000
这种情况下当OldestXmin > 150000000 = 548
也就是当前最小的活跃事务大于1亿4千万+的时候才会触发eager freeze。