Обычная очистка

Создадим таблицу и индекс.

=> CREATE DATABASE mvcc_vacuum;
CREATE DATABASE
=> \c mvcc_vacuum
You are now connected to database "mvcc_vacuum" as user "postgres".
=> CREATE TABLE t(id integer);
CREATE TABLE
=> CREATE INDEX t_id ON t(id);
CREATE INDEX

Как обычно, используем расширение pageinspect...

=> CREATE EXTENSION pageinspect;
CREATE EXTENSION

...представление для табличной страницы...

=> CREATE VIEW t_v AS
SELECT '(0,'||lp||')' AS ctid,
       CASE lp_flags
         WHEN 0 THEN 'unused'
         WHEN 1 THEN 'normal'
         WHEN 2 THEN 'redirect to '||lp_off
         WHEN 3 THEN 'dead'
       END AS state,
       t_xmin || CASE
         WHEN (t_infomask & 256) > 0 THEN ' (c)'
         WHEN (t_infomask & 512) > 0 THEN ' (a)'
         ELSE ''
       END AS xmin,
       t_xmax || CASE
         WHEN (t_infomask & 1024) > 0 THEN ' (c)'
         WHEN (t_infomask & 2048) > 0 THEN ' (a)'
         ELSE ''
       END AS xmax,
       CASE WHEN (t_infomask2 & 16384) > 0 THEN 't' END AS hhu,
       CASE WHEN (t_infomask2 & 32768) > 0 THEN 't' END AS hot,
       t_ctid
FROM heap_page_items(get_raw_page('t',0))
ORDER BY lp;
CREATE VIEW

...и представление для индекса.

=> CREATE VIEW t_id_v AS
SELECT itemoffset,
       ctid
FROM bt_page_items('t_id',1);
CREATE VIEW

Вставим строку в таблицу и обновим ее:

=> INSERT INTO t VALUES (1);
INSERT 0 1
=> UPDATE t SET id = 2;
UPDATE 1

Теперь, чтобы еще раз напомнить про понятие "горизонта событий", откроем в другом сеансе транзакцию с активным снимком данных.

=> \c mvcc_vacuum
You are now connected to database "mvcc_vacuum" as user "postgres".
=> BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN
=> SELECT * FROM t;
 id 
----
  2
(1 row)


Горизонт для базы данных определяется этим снимком:

=> SELECT backend_xmin FROM pg_stat_activity WHERE pid = pg_backend_pid();
 backend_xmin 
--------------
          610
(1 row)


Снова обновим строку.

=> UPDATE t SET id = 3;
UPDATE 1

Сейчас в таблице три версии строки:

=> SELECT * FROM t_v;
 ctid  | state  |  xmin   |  xmax   | hhu | hot | t_ctid 
-------+--------+---------+---------+-----+-----+--------
 (0,1) | normal | 608 (c) | 609 (c) |     |     | (0,2)
 (0,2) | normal | 609 (c) | 610     |     |     | (0,3)
 (0,3) | normal | 610     | 0 (a)   |     |     | (0,3)
(3 rows)


Выполним теперь очистку.

=> VACUUM t;
VACUUM

Как изменится табличная страница?


=> SELECT * FROM t_v;
 ctid  | state  |  xmin   |  xmax   | hhu | hot | t_ctid 
-------+--------+---------+---------+-----+-----+--------
 (0,1) | unused |         |         |     |     | 
 (0,2) | normal | 609 (c) | 610 (c) |     |     | (0,3)
 (0,3) | normal | 610 (c) | 0 (a)   |     |     | (0,3)
(3 rows)

Очистка освободила одну версию строки, а вторая осталась без изменений, так как параллельная транзакция до сих пор не завершена и ее снимок активен.


В индексе - два указателя на оставшиеся версии:

=> SELECT * FROM t_id_v;
 itemoffset | ctid  
------------+-------
          1 | (0,2)
          2 | (0,3)
(2 rows)


Можно попросить очистку рассказать о том, что происходит:

=> VACUUM VERBOSE t;
INFO:  vacuuming "public.t"
INFO:  index "t_id" now contains 2 row versions in 2 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  "t": found 0 removable, 2 nonremovable row versions in 1 out of 1 pages
DETAIL:  1 dead row versions cannot be removed yet, oldest xmin: 610
There were 1 unused item pointers.
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

Обратите внимание:


Теперь завершим параллельную транзакцию и снова вызовем очистку.

=> COMMIT;
COMMIT
=> VACUUM VERBOSE t;
INFO:  vacuuming "public.t"
INFO:  scanned index "t_id" to remove 1 row versions
DETAIL:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO:  "t": removed 1 row versions in 1 pages
DETAIL:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO:  index "t_id" now contains 1 row versions in 2 pages
DETAIL:  1 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  "t": found 1 removable, 1 nonremovable row versions in 1 out of 1 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 611
There were 1 unused item pointers.
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

Теперь в странице осталась только последняя актуальная версия строки:

=> SELECT * FROM t_v;
 ctid  | state  |  xmin   | xmax  | hhu | hot | t_ctid 
-------+--------+---------+-------+-----+-----+--------
 (0,1) | unused |         |       |     |     | 
 (0,2) | unused |         |       |     |     | 
 (0,3) | normal | 610 (c) | 0 (a) |     |     | (0,3)
(3 rows)

В индексе также только одна запись:

=> SELECT * FROM t_id_v;
 itemoffset | ctid  
------------+-------
          1 | (0,3)
(1 row)


Полная очистка

Файлы, занимаемые таблицей и индексом:

=> SELECT pg_relation_filepath('t'), pg_relation_filepath('t_id');
 pg_relation_filepath | pg_relation_filepath 
----------------------+----------------------
 base/16495/16496     | base/16495/16499
(1 row)


Вызываем полную очистку.

=> VACUUM FULL VERBOSE t;
INFO:  vacuuming "public.t"
INFO:  "t": found 0 removable, 1 nonremovable row versions in 1 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM

Таблица и индекс теперь полностью перестроены:

=> SELECT * FROM t_v;
 ctid  | state  |  xmin   | xmax  | hhu | hot | t_ctid 
-------+--------+---------+-------+-----+-----+--------
 (0,1) | normal | 610 (c) | 0 (a) |     |     | (0,1)
(1 row)

=> SELECT * FROM t_id_v;
 itemoffset | ctid  
------------+-------
          1 | (0,1)
(1 row)


Имена файлов поменялись:

=> SELECT pg_relation_filepath('t'), pg_relation_filepath('t_id');
 pg_relation_filepath | pg_relation_filepath 
----------------------+----------------------
 base/16495/16536     | base/16495/16539
(1 row)


Конец демонстрации.