Создадим таблицу с двумя полями и индекс.
Параметр 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(id integer, s char(2000)) WITH (fillfactor = 75);
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(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_id_v;
itemoffset | ctid ------------+------- 1 | (0,1) (1 row)
В табличной странице - версии строки, объединенные в список:
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+--------+-----------+-----------+-----+-----+-------- (0,1) | normal | 55487 (c) | 55488 (c) | t | | (0,2) (0,2) | normal | 55488 (c) | 55489 (c) | t | t | (0,3) (0,3) | normal | 55489 (c) | 55490 | t | t | (0,4) (0,4) | normal | 55490 | 0 (a) | | t | (0,4) (4 rows)
Как и в демонстрации, еще одно обновление строки приводит к внутристраничной очистке:
=> UPDATE t SET s = 'E';
UPDATE 1
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+---------------+-----------+-------+-----+-----+-------- (0,1) | redirect to 4 | | | | | (0,2) | normal | 55491 | 0 (a) | | t | (0,2) (0,3) | unused | | | | | (0,4) | normal | 55490 (c) | 55491 | t | t | (0,2) (4 rows)
Обратите внимание, что:
Выполним обновление еще несколько раз:
=> UPDATE t SET s = 'F';
UPDATE 1
=> UPDATE t SET s = 'G';
UPDATE 1
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+---------------+-----------+-----------+-----+-----+-------- (0,1) | redirect to 4 | | | | | (0,2) | normal | 55491 (c) | 55492 (c) | t | t | (0,3) (0,3) | normal | 55492 (c) | 55493 | t | t | (0,5) (0,4) | normal | 55490 (c) | 55491 (c) | t | t | (0,2) (0,5) | normal | 55493 | 0 (a) | | t | (0,5) (5 rows)
Следующее обновление снова вызывает очистку:
=> UPDATE t SET s = 'H';
UPDATE 1
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+---------------+-----------+-------+-----+-----+-------- (0,1) | redirect to 5 | | | | | (0,2) | normal | 55494 | 0 (a) | | t | (0,2) (0,3) | unused | | | | | (0,4) | unused | | | | | (0,5) | normal | 55493 (c) | 55494 | t | t | (0,2) (5 rows)
Теперь, чтобы HOT-очистка не смогла освободить место, начнем параллельную транзакцию и построим в ней снимок данных.
=> \c mvcc_hot
You are now connected to database "mvcc_hot" as user "postgres".
=> BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN
=> SELECT count(*) FROM t;
count ------- 1 (1 row)
Теперь выполняем обновление в первом сеансе:
=> UPDATE t SET s = 'I';
UPDATE 1
=> UPDATE t SET s = 'J';
UPDATE 1
=> UPDATE t SET s = 'K';
UPDATE 1
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+---------------+-----------+-----------+-----+-----+-------- (0,1) | redirect to 2 | | | | | (0,2) | normal | 55494 (c) | 55495 (c) | t | t | (0,3) (0,3) | normal | 55495 (c) | 55496 (c) | t | t | (0,4) (0,4) | normal | 55496 (c) | 55497 | t | t | (0,5) (0,5) | normal | 55497 | 0 (a) | | t | (0,5) (5 rows)
Следующее обновление уже не сможет освободить место на странице:
=> UPDATE t SET s = 'L';
UPDATE 1
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | hhu | hot | t_ctid -------+---------------+-----------+-----------+-----+-----+-------- (0,1) | redirect to 2 | | | | | (0,2) | normal | 55494 (c) | 55495 (c) | t | t | (0,3) (0,3) | normal | 55495 (c) | 55496 (c) | t | t | (0,4) (0,4) | normal | 55496 (c) | 55497 (c) | t | t | (0,5) (0,5) | normal | 55497 (c) | 55498 | | t | (1,1) (5 rows)
Видим ссылку (1,1), ведущую на страницу 1.
Теперь в индексе - две строки, каждая указывает на начало своей HOT-цепочки:
=> SELECT * FROM t_id_v;
itemoffset | ctid ------------+------- 1 | (1,1) 2 | (0,1) (2 rows)
=> COMMIT;
COMMIT