Создадим таблицу и индекс.
=> 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)
Конец демонстрации.