作者
digoal
日期
2019-08-07
标签
PostgreSQL , freeze , 2pc , long query , long xact
背景
freeze时,可以降到什么年龄?哪些tuple的infomask可以标记为frozen状态?
先补一下背景原理:
《空闲事务、慢2pc - 膨胀点内核优化思路、参数优化 - GetOldestXmin优化》
《膨胀点解释 - 全局catalog,库级catalog,普通表,wal文件 - 哪些垃圾(dead tuple), wal文件不能被回收reuse - 什么情况下可能膨胀》
《解读用户最常问的PostgreSQL垃圾回收、膨胀、多版本管理、存储引擎等疑惑 - 经典》
《PostgreSQL 自动vacuum(垃圾回收)与手动vacuum的参数详解》
结论:
freeze时,表中的tuple,哪些比数据库中最老事务快照还老的记录,可以被标记为frozen,因为他们对这些事务来说是绝对可见的,所以标记为frozen没有问题。而比最老事务快照更年轻(也就是在最老事务之后产生的版本,它们不一定被这些老事务绝对可见,所以不能标记为frozen)。
因此,一个数据库中有一个1小时前的事务,那么freeze时,最近一小时产生的版本都不能标记为frozen,如果最近1小时产生了21亿(模糊值,32位无符号整型的一半)个事务,数据库就会因为无法frozen而强制停库(必须在单用户进去freeze)。
例子
1、产生事务的压测脚本
vi test.sql
select txid_current();
2、新建一个事务,不关
```
postgres=# create table ta1(id int);
CREATE TABLE
postgres=# begin;
BEGIN
postgres=# insert into ta1 values (1);
INSERT 0 1
postgres=# select txid_current();
txid_current
123567308
复制
(1 row)
```
3、新建另一个表,写入一条记录(这条记录的版本是在最老事务之后产生的)
postgres=# create table ta2 (id int);
CREATE TABLE
postgres=# insert into ta2 values (100);
INSERT 0 1
4、开启一段压测,消耗大量事务号
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 32 -j 32 -T 10
5、消耗大量事务后,再新建另一个事务,不关
```
postgres=# begin;
BEGIN
postgres=# insert into ta1 values (2);
INSERT 0 1
postgres=# select txid_current();
txid_current
142973803
复制
(1 row)
```
6、继续消耗大量事务
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 32 -j 32 -T 10
7、freeze ta2这个表,你会发现年龄始终降不下来。因为前面说的原因:表的tuple如果是在最老事务之前产生的,这个tuple可以被标记为frozen,而在最老事务之后产生的tuple,必须保留版本。
```
postgres=# vacuum (freeze,verbose) ta2;
psql: INFO: aggressively vacuuming "public.ta2"
psql: INFO: "ta2": found 0 removable, 1 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 123567308
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM
postgres=# select age(relfrozenxid),relname,pg_size_pretty(pg_total_relation_size(oid)) from pg_class where relname='ta2';
age | relname | pg_size_pretty
----------+---------+----------------
26970954 | ta2 | 40 kB
(1 row)
```
8、释放第一个事务,freeze ta2。这个时候最老的事务是在ta2的所有记录版本之后发起的。理论上执行vacuum freeze后,这个表的年龄应该可以降到0.
```
postgres=# vacuum (freeze,verbose) ta2;
psql: INFO: aggressively vacuuming "public.ta2"
psql: INFO: "ta2": found 0 removable, 1 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 142973803
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM
postgres=# select age(relfrozenxid),relname,pg_size_pretty(pg_total_relation_size(oid)) from pg_class where relname='ta2';
age | relname | pg_size_pretty
---------+---------+----------------
7564459 | ta2 | 40 kB
(1 row)
```
实际上,你会发现年龄没有降到0,而是当前事务号减去最老事务号(也就是最老事务号,到当前事务号,过去了多少个事务)的意思。
```
postgres=# select txid_current();
txid_current
150538262
复制
(1 row)
postgres=# select 150538262-142973803;
?column?
7564459
(1 row)
```
这是为什么呢?为什么不是0呢?可以看一下源代码。
估计虽然表的记录infomask标记了frozen,但是原数据pg_class.relfrozenxid取了freeze时,系统中的最老事务号。所以看年龄就没有降到0.
最后,如果真的年龄快到极限了吗,怎么办?看完应该明了了。如果被迫数据库停库了,就停库,启动数据库单用户模式去vacuum freeze。
PostgreSQL 许愿链接
您的愿望将传达给PG kernel hacker、数据库厂商等, 帮助提高数据库产品质量和功能, 说不定下一个PG版本就有您提出的功能点. 针对非常好的提议,奖励限量版PG文化衫、纪念品、贴纸、PG热门书籍等,奖品丰富,快来许愿。开不开森.