之前学习pg vacuum的时候,只是简单的知道pg的mvcc会产生多版本,多版本会有垃圾,垃圾需要处理,vacuum就可以清除这些垃圾。可是一直没有仔细想过到底哪些垃圾是可以清除的,哪些是不能处理的。下面我们仔细研究下这个问题,vacuum可以降低表的年龄可以释放空闲空间,也可以垃圾回收,我们先只单独讨论垃圾回收这一过程。
首先什么样的数据是垃圾数据:
postgres=# create table a(id int);
CREATE TABLE
postgres=# insert into a values(1);
INSERT 0 1
postgres=# select *,ctid from a;
id | ctid |
---|---|
1 | (0,1) |
(1 row)
创建好测试表插入第一条数据,这个数据是在0号页面第一行上。
下面更新下这条数据
postgres=# update a set id=10;
UPDATE 1
postgres=# select *,ctid from a;
id | ctid |
---|---|
10 | (0,2) |
(1 row)
发现,数据记录在0号页面的第二行上了,原来第一行的记录被更新了将更新的数据放到了第二行上,所以之前的第一条记录就已经没用了,是一条垃圾记录了。但是这时候这条记录并没有被回收,只是隐藏了,下面看下回收的过程:
postgres=# vacuum verbose a;
INFO: vacuuming "public.a"
INFO: "a": found 1 removable, 1 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
VACUUM
可以看到有一条记录被移除了。那是不是有垃圾无法被vacuum回收的情况呢?是有,下面分两种不同版本的来分析下,pg9.4和pg9.6因为pg9.6对快照过旧做了一些改动。先说9.4吧。
9.4:
先模拟不能回收的情况:
会话A:
postgres=# begin;
BEGIN
postgres=# select txid_current();
txid_current
98978845
(1 row)
postgres=# select pg_backend_pid();
pg_backend_pid
31156
(1 row)
postgres=#
会话A开始一个事务,申请一个事务号,产看下会话A的pid是多少,不要提交。
会话B:
postgres=# select backend_xid,backend_xmin from pg_stat_activity where pid=31156;
backend_xid | backend_xmin |
---|---|
98978850 |
(1 row)
postgres=# select txid_current();
txid_current
98978851
(1 row)
postgres=# update a set id =1000;
UPDATE 1
postgres=# select *,ctid from a;
id | ctid |
---|---|
1000 | (0,2) |
(1 row)
postgres=# vacuum verbose a;
INFO: vacuuming "public.a"
INFO: "a": found 0 removable, 2 nonremovable row versions in 1 out of 1 pages
DETAIL: 1 dead row versions cannot be removed yet.
There were 1 unused item pointers.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
VACUUM
postgres=#
会话B先看下会话A的backend_pid和backend_xmin是否有值,然后看下当前最新事务号,更新下a表后进行vacuum,发现有一条垃圾记录无法回收。这个是为什么呢?先说下backend_pid和backend_xmin,backend_pid是当开启一个事务时,申请到事务号是就会有值(是指begin后又插入或者更新操作等),backend_xmin在read commit级别下是每条语句都会有快照,但是语句执行完马上就释放,长时间执行语句是可以看到该值:
会话A:
postgres=# select pg_backend_pid();
pg_backend_pid
11194
(1 row)
postgres=# select pg_sleep(20);
会话B:
postgres=# select backend_xid,backend_xmin from pg_stat_activity where pid=11194;
backend_xid | backend_xmin |
---|---|
98979813 |
(1 row)
当repeatable read和串行化的隔离级别中,开启一个事务,任意一条语句都会申请到backend_xmin。
呢垃圾回收和这两个值有什么关系呢?9.6以前是只能回收tuple的xmax小于min(backend_xid,backend_xmin)的 ,所以当你有一个长事务时长时间不提交,它之后更新删除的xmax都会大于呢个长事务的backend_xid所以这些垃圾数据就都无法回收,造成表膨胀。当你备份时事务的隔离级别会是repeatable read,时间过长和上面一样会产生表膨胀。
下面说下9.6的变化吧,9.6新增了old_snapshot_theshold,这个是表示当语句持有backend_xmin的时间超过old_snapshot_theshold设定的时间并且读取到的数据块的lsn大于backend_xmin快照存储的lsn时就会发生snapshot too old的情况。下面模拟下这种情况。
会话A:
postgres=# begin transaction isolation level repeatable read;
BEGIN
postgres=# select 1;
?column?
1
(1 row)
postgres=# select *,ctid from a where ctid::text like '%(1427,%' limit 10;
id | ctid |
---|---|
4735747 | (1427,1) |
1209750 | (1427,2) |
3293719 | (1427,3) |
555445 | (1427,4) |
3724432 | (1427,5) |
3114037 | (1427,6) |
182685 | (1427,7) |
213731 | (1427,8) |
4052679 | (1427,9) |
1677525 | (1427,10) |
(10 rows)
postgres=# select *,ctid from a where ctid::text like '%(1427,%' limit 10;
ERROR: snapshot too old
STATEMENT: select *,ctid from a where ctid::text like '%(1427,%' limit 10;
ERROR: snapshot too old
会话B:
postgres=# select backend_xid,backend_xmin from pg_stat_activity where pid=30504;
backend_xid | backend_xmin |
---|---|
109690248 |
(1 row)
postgres=# update a set id =1090 where id =4735747;
UPDATE 1
postgres=#
会话A先开启repeatable read隔离级别得到一个backend_xmin,扫描1427号页面,B会话更新1427号页面任意一行记录,等到一个old_snapshot_theshold的时间,再去A会话里查找1427号页面就会发现snapshot too old这个错。
那这个对垃圾回收有哪些影响呢?之前是看tuple的xmax是否大于min(backend_xid,backend_xmin),大于就无法回收,现在是tuple的xmax是否大于
max(old_snapshot_theshold中记录的最老记录,backend_xid,backend_xmin),
old_snapshot_theshold是每分钟记录最大的backend_xid没有取当前最小事务号.
下面模拟9.6新增可以回收的情况:
会话A:
postgres=# begin;
BEGIN
postgres=# select pg_sleep(200);
会话B:
postgres=# insert into a values(1);
INSERT 0 1
postgres=# insert into a values(1);
INSERT 0 1
postgres=# delete from a where id=1;
DELETE 2
postgres=# select * from a limit 1;
id
3137507
(1 row)
postgres=# vacuum verbose a;
INFO: vacuuming "public.a"
INFO: "a": found 0 removable, 452 nonremovable row versions in 2 out of 1428 pages
DETAIL: 2 dead row versions cannot be removed yet.
There were 2 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
VACUUM
postgres=# vacuum verbose a;
INFO: vacuuming "public.a"
INFO: "a": removed 2 row versions in 2 pages
INFO: "a": found 2 removable, 450 nonremovable row versions in 2 out of 1428 pages
DETAIL: 0 dead row versions cannot be removed yet.
There were 2 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
VACUUM
postgres=#
完整过程是先,开启A会话,A会话得到pg_backend_xmin,B会话插入记录并删除,进行第一次vacuum发现呢条记录无法回收,等A运行一段时间old_snapshot_theshold获取到的backend_xid大于backend_xmin就可以回收这些垃圾记录了, 需要先查一下这个表才能回收(不知道什么原因)。