Создадим таблицу и индекс. Каждая строка таблицы состоит из 2000 символов; если использовать только английские буквы, то версия строки будет занимать 2000 байт плюс заголовок.
Параметр fillfactor установим в 75 %.
=> CREATE DATABASE mvcc_hot;
CREATE DATABASE
=> \c mvcc_hot
You are now connected to database "mvcc_hot" as user "postgres".
=> CREATE TABLE t(s char(2000)) WITH (fillfactor = 75);
CREATE TABLE
=> CREATE INDEX t_s ON t(s);
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_s_v AS SELECT itemoffset, ctid FROM bt_page_items('t_s',1);
CREATE VIEW
Проверим, как работает внутристраничная очистка. Для этого вставим одну строку и несколько раз изменим ее:
=> INSERT INTO t(s) VALUES ('A');
INSERT 0 1
=> UPDATE t SET s = 'B';
UPDATE 1
=> UPDATE t SET s = 'C';
UPDATE 1
=> UPDATE t SET s = 'D';
UPDATE 1
В странице сейчас четыре версии строки:
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+---------+---------+-----+-----+-------- (0,1) | normal | 592 (c) | 593 (c) | | | (0,2) (0,2) | normal | 593 (c) | 594 (c) | | | (0,3) (0,3) | normal | 594 (c) | 595 | | | (0,4) (0,4) | normal | 595 | 0 (a) | | | (0,4) (4 rows)
(К двум последним версиям строк еще не было обращений, поэтому статус транзакции в них пока не проставлен.)
На самом деле мы только что превысили порог fillfactor. На это указывает разница между значениями pagesize и upper, равная 8192-80=8112. При этом 75 % от размера страницы составляет 6144 байтов.
=> SELECT lower, upper, pagesize FROM page_header(get_raw_page('t',0));
lower | upper | pagesize -------+-------+---------- 40 | 80 | 8192 (1 row)
Проверим это.
=> UPDATE t SET s = 'E';
UPDATE 1
Какие изменения произойдут со страницей?
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+---------+-------+-----+-----+-------- (0,1) | dead | | | | | (0,2) | dead | | | | | (0,3) | dead | | | | | (0,4) | normal | 595 (c) | 596 | | | (0,5) (0,5) | normal | 596 | 0 (a) | | | (0,5) (5 rows)
Все неактуальные версии строк (0,1), (0,2) и (0,3) очищены; после этого на освободившееся место добавлена новая версия строки (0,5).
Указатели на очищенные строки не освобождены, а имеют статус dead.
Заглянем в индекс:
=> SELECT * FROM t_s_v;
itemoffset | ctid ------------+------- 1 | (0,1) 2 | (0,2) 3 | (0,3) 4 | (0,4) 5 | (0,5) (5 rows)
Индексные строки ссылаются на каждую из пяти версий строк (в том числе на три уже удаленные из табличной страницы) - внутристраничная очистка не выходит за рамки табличной страницы.
Чтобы посмотреть на работу HOT-обновления, удалим индекс и очистим таблицу.
=> DROP INDEX t_s;
DROP INDEX
=> TRUNCATE TABLE t;
TRUNCATE TABLE
Повторим вставку и обновление строки.
=> INSERT INTO t(s) VALUES ('A');
INSERT 0 1
=> UPDATE t SET s = 'B';
UPDATE 1
Вот что мы видим в табличной странице:
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+---------+-------+-----+-----+-------- (0,1) | normal | 599 (c) | 600 | t | | (0,2) (0,2) | normal | 600 | 0 (a) | | t | (0,2) (2 rows)
В странице - цепочка изменений:
При дальнейших изменениях цепочка будет расти (в пределах страницы):
=> UPDATE t SET s = 'C';
UPDATE 1
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+---------+---------+-----+-----+-------- (0,1) | normal | 599 (c) | 600 (c) | t | | (0,2) (0,2) | normal | 600 (c) | 601 | t | t | (0,3) (0,3) | normal | 601 | 0 (a) | | t | (0,3) (3 rows)
Конец демонстрации.