СТРАНИЦЫ И ВЕРСИИ СТРОК ~~~~~~~~~~~~~~~~~~~~~~~ Создадим базу данных, в ней таблицу и индекс по одному из полей. => 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) Конец демонстрации. .......................................................................