Создадим таблицу и индекс по одному из полей.
=> CREATE DATABASE mvcc_tuples;
CREATE DATABASE
=> \c mvcc_tuples
You are now connected to database "mvcc_tuples" 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, 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 ('FOO');
INSERT 0 1
Вот номер нашей текущей транзакции:
=> SELECT txid_current();
txid_current -------------- 578 (1 row)
Вот что содержится в странице:
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | xmin_c | xmin_a | xmax_c | xmax_a | t_ctid -------+--------+------+------+--------+--------+--------+--------+-------- (0,1) | normal | 578 | 0 | | | | t | (0,1) (1 row)
Похожую, но существенно менее детальную информацию можно получить и из самой таблицы, используя псевдостолбцы xmin и xmax:
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s ------+------+----+----- 578 | 0 | 1 | FOO (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 | t_ctid -------+--------+------+------+--------+--------+--------+--------+-------- (0,1) | normal | 578 | 0 | | | | t | (0,1) (1 row)
Ничего, так как единственная операция, которая выполняется при фиксации - запись статуса транзакции в XACT.
Транзакция, первой обратившаяся к странице, должна будет определить статус транзакции xmin. Этот статус будет записан в информационные биты:
=> SELECT * FROM t;
id | s ----+----- 1 | FOO (1 row)
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | xmin_c | xmin_a | xmax_c | xmax_a | t_ctid -------+--------+------+------+--------+--------+--------+--------+-------- (0,1) | normal | 578 | 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 | t_ctid -------+--------+------+------+--------+--------+--------+--------+-------- (0,1) | normal | 578 | 579 | t | | | | (0,1) (1 row)
При откате xmax остается...
=> ROLLBACK;
ROLLBACK
=> SELECT * from t_v;
ctid | state | xmin | xmax | xmin_c | xmin_a | xmax_c | xmax_a | t_ctid -------+--------+------+------+--------+--------+--------+--------+-------- (0,1) | normal | 578 | 579 | t | | | | (0,1) (1 row)
При этом при обращении к странице выставляется соответствующий бит:
=> SELECT * FROM t;
id | s ----+----- 1 | FOO (1 row)
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | xmin_c | xmin_a | xmax_c | xmax_a | t_ctid -------+--------+------+------+--------+--------+--------+--------+-------- (0,1) | normal | 578 | 579 | t | | | t | (0,1) (1 row)
Теперь проверим обновление.
=> UPDATE t SET s = 'BAR';
UPDATE 1
Запрос выдает одну строку (новую версию):
=> SELECT * FROM t;
id | s ----+----- 1 | BAR (1 row)
Но в странице мы видим обе версии:
=> SELECT * FROM t_v;
ctid | state | xmin | xmax | xmin_c | xmin_a | xmax_c | xmax_a | t_ctid -------+--------+------+------+--------+--------+--------+--------+-------- (0,1) | normal | 578 | 580 | t | | t | | (0,2) (0,2) | normal | 580 | 0 | t | | | t | (0,2) (2 rows)
Причем новый номер транзакции записался на место старого (поскольку старая транзакция была отменена).
При этом в индексной странице обнаруживаем указатели на обе версии:
=> SELECT * FROM t_s_v;
itemoffset | ctid ------------+------- 1 | (0,2) 2 | (0,1) (2 rows)
Индексные записи внутри страницы упорядочены. Поэтому первой идет запись с ключом BAR (ссылается на версию (0,2)), а второй - запись с ключом FOO (ссылается на версию (0,1)).
В заключение посмотрим на поля заголовка страницы, определяющие границы ее областей.
=> SELECT lower, upper, special, pagesize FROM page_header(get_raw_page('t',0));
lower | upper | special | pagesize -------+-------+---------+---------- 32 | 8128 | 8192 | 8192 (1 row)
Области занимают следующие диапазоны адресов:
Конец демонстрации.