Внутристраничная очистка

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

Чтобы посмотреть на работу 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)


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