
СТРАНИЦЫ И ВЕРСИИ СТРОК
~~~~~~~~~~~~~~~~~~~~~~~

Создадим базу данных, в ней таблицу и индекс по одному из полей.

        => create database db3;
        CREATE DATABASE

        => \c db3
        You are now connected to database "db3" as user "postgres".

        => create table t(id serial, s text);
        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 as xmin,
        =>          t_xmax as xmax,
        =>          case when (t_infomask & 256) > 0 then 't' end as xmin_c,
        =>          case when (t_infomask & 512) > 0 then 't' end as xmin_a,
        =>          case when (t_infomask & 1024) > 0 then 't' end as xmax_c,
        =>          case when (t_infomask & 2048) > 0 then 't' end as xmax_a,
        =>          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

(Нулевая страница индекса содержит метаинформацию, поэтому смотрим в первую.)

.......................................................................

Вставим одну строку, предварительно начав транзакцию.

        => begin;
        BEGIN

        => insert into t(s) values ('AAA');
        INSERT 0 1

Вот номер нашей текущей транзакции:

        => select txid_current();
         txid_current 
        --------------
               554665
        (1 row)
        

.......................................................................

Вот что содержится в странице:

        => select * from t_v;
         ctid  | state  |  xmin  | xmax | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554665 |    0 |        |        |        | t      |     |     | (0,1)
        (1 row)
        

Похожую, но существенно менее детальную информацию можно получить и из самой таблицы,
используя псевдостолбцы xmin и xmax:

        => select xmin, xmax, * from t;
          xmin  | xmax | id |  s  
        --------+------+----+-----
         554665 |    0 |  1 | AAA
        (1 row)
        

.......................................................................

В индексной странице видим один указатель на единственную строку таблицы:

        => select * from t_s_v;
         itemoffset | ctid  
        ------------+-------
                  1 | (0,1)
        (1 row)
        

.......................................................................

Зафиксируем изменение.

        => commit;
        COMMIT

Что изменилось?

        => select * from t_v;
         ctid  | state  |  xmin  | xmax | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554665 |    0 |        |        |        | t      |     |     | (0,1)
        (1 row)
        

.......................................................................

Ничего, так как единственная операция, которая выполняется при фиксации -
запись статуса транзакции в CLOG.
С одной стороны, это хорошо, потому что фиксация происходит быстро.
С другой - транзакция, первой обратившаяся к странице, будет вынуждена определить
статус транзакции xmin. Этот статус будет записан в информационные биты:

        => select * from t;
         id |  s  
        ----+-----
          1 | AAA
        (1 row)
        

        => select * from t_v;
         ctid  | state  |  xmin  | xmax | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554665 |    0 | t      |        |        | t      |     |     | (0,1)
        (1 row)
        

.......................................................................

Теперь удалим строку.

        => begin;
        BEGIN

        => delete from t;
        DELETE 1

Номер транзакции записался в поле xmax:

        => select * from t_v;
         ctid  | state  |  xmin  |  xmax  | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+--------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554665 | 554666 | t      |        |        |        |     |     | (0,1)
        (1 row)
        

.......................................................................

При откате xmax остается...

        => rollback;
        ROLLBACK

        => select * from t_v;
         ctid  | state  |  xmin  |  xmax  | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+--------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554665 | 554666 | t      |        |        |        |     |     | (0,1)
        (1 row)
        

.......................................................................

При этом при обращении к странице выставляется соответствующий бит:

        => select * from t;
         id |  s  
        ----+-----
          1 | AAA
        (1 row)
        

        => select * from t_v;
         ctid  | state  |  xmin  |  xmax  | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+--------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554665 | 554666 | t      |        |        | t      |     |     | (0,1)
        (1 row)
        

.......................................................................

Теперь проверим обновление.

        => update t set s = 'BBB';
        UPDATE 1

.......................................................................

Запрос выдает одну строку (новую версию):

        => select * from t;
         id |  s  
        ----+-----
          1 | BBB
        (1 row)
        

Но в странице мы видим обе версии:

        => select * from t_v;
         ctid  | state  |  xmin  |  xmax  | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+--------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554665 | 554667 | t      |        | t      |        |     |     | (0,2)
         (0,2) | normal | 554667 |      0 | t      |        |        | t      |     |     | (0,2)
        (2 rows)
        

Причем новый номер транзакции записался на место старого
(поскольку старая транзакция была откачена).

.......................................................................

При этом в индексной странице обнаруживаем указатели на обе версии:

        => select * from t_s_v;
         itemoffset | ctid  
        ------------+-------
                  1 | (0,1)
                  2 | (0,2)
        (2 rows)
        

.......................................................................

Если бы индекс был построен по другому полю (которое не изменилось при обновлении),
то не было бы смысла создавать в индексе два указателя.
Такая оптимизация называется HOT-обновление. Для того, чтобы проверить ее,
очистим таблицу и создадим другой индекс (по полу id).

        => drop index t_s;
        DROP INDEX

        => truncate table t;
        TRUNCATE TABLE

        => create index t_id on t(id);
        CREATE INDEX

        => create view t_id_v as
        =>   select itemoffset,
        =>          ctid
        =>   from bt_page_items('t_id',1);
        CREATE VIEW

.......................................................................

Повторим вставку и обновление строки.

        => insert into t(s) values ('AAA');
        INSERT 0 1

        => update t set s = 'BBB';
        UPDATE 1

        => select * from t;
         id |  s  
        ----+-----
          2 | BBB
        (1 row)
        

.......................................................................

Вот что мы видим в страницах таблицы и индекса:

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

        => select * from t_v;
         ctid  | state  |  xmin  |  xmax  | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+--------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554672 | 554673 | t      |        | t      |        | t   |     | (0,2)
         (0,2) | normal | 554673 |      0 | t      |        |        | t      |     | t   | (0,2)
        (2 rows)
        

В индексе один указатель (на первую версию), а в странице - цепочка изменений:
* флаг heap hot updated показывает, что надо идти по цепочке,
* флаг heap only tuple показывает, что на данную версию строки нет ссылок из индексов.

.......................................................................

При дальнейших изменениях цепочка будет расти (в пределах страницы):

        => update t set s = 'CCC';
        UPDATE 1

.......................................................................

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

        => select * from t_v;
         ctid  | state  |  xmin  |  xmax  | xmin_c | xmin_a | xmax_c | xmax_a | hhu | hot | t_ctid 
        -------+--------+--------+--------+--------+--------+--------+--------+-----+-----+--------
         (0,1) | normal | 554672 | 554673 | t      |        | t      |        | t   |     | (0,2)
         (0,2) | normal | 554673 | 554674 | t      |        |        |        | t   | t   | (0,3)
         (0,3) | normal | 554674 |      0 |        |        |        | t      |     | t   | (0,3)
        (3 rows)
        

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

.......................................................................
