Значения поля xmax

Предположим, имеется две транзакции: Т1 и Т2.

BEGIN; -- Т1

UPDATE table
SET amount = 1
WHERE id = 1;

SELECT xmin, xmax FROM table
WHERE id = 1; 
-- получаем: xmin = xid транзакции Т1, xmax = 0

Начинаем вторую транзакцию:

BEGIN; -- T2

UPDATE table
SET amount = 2
WHERE id = 1; 
-- ожидание завершения транзакции Т1 

Завершаем первую транзакцию:

COMMIT;

Продолжаем вторую:

SELECT xmin, xmax FROM table
WHERE id = 1; 
-- получаем: xmin и xmax равный идентификатору (xid) транзакции Т2.

Вопрос: почему транзакция T1 после своего обновления начинает видеть xmax = 0, тогда как транзакция T2 после своего обновления начинает видеть xmax = xid(T2)? Хочу подчеркнуть, что эти значения видны относительно той транзакции, которая обновляла данные.

P.S. Я как-то раньше не уделял этому внимания и всегда считал, что если транзакция изменила строку, то относительно нее самой она будет видеть в новой строке xmax = 0, так как это новый кортеж и его никто еще не изменял.


Ответы (1 шт):

Автор решения: Мелкий

xmin и xmax хороши для объяснения как реализована MVCC, но в реальном коде всё сложнее. Прояснить происходящее в этой ситуации поможет pageinspect, с помощью которого заглянем в infomask:

melkij=> create table "table" (id int, amount int);
CREATE TABLE

melkij=> insert into "table" values (1,2);
INSERT 0 1

melkij=# SELECT t_ctid, t_xmin, t_xmax, raw_flags, combined_flags
         FROM heap_page_items(get_raw_page('table', 0)),
           LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2)
         WHERE t_infomask IS NOT NULL OR t_infomask2 IS NOT NULL;
 t_ctid | t_xmin | t_xmax |      raw_flags      | combined_flags 
--------+--------+--------+---------------------+----------------
 (0,1)  |    864 |      0 | {HEAP_XMAX_INVALID} | {}

Здесь только что вставленная строка будет иметь xmin той транзакции, которая произвела insert, а xmax не будет.

# T1
melkij=> begin;
BEGIN
melkij=*> UPDATE "table"
SET amount = 1
WHERE id = 1;
UPDATE 1

# T2
melkij=> begin;
BEGIN
melkij=*> UPDATE "table"
SET amount = 2
WHERE id = 1;

 t_ctid | t_xmin | t_xmax |                    raw_flags                     | combined_flags 
--------+--------+--------+--------------------------------------------------+----------------
 (0,2)  |    864 |    865 | {HEAP_XMIN_COMMITTED,HEAP_HOT_UPDATED}           | {}
 (0,2)  |    865 |      0 | {HEAP_XMAX_INVALID,HEAP_UPDATED,HEAP_ONLY_TUPLE} | {}

# T1 commit

 t_ctid | t_xmin | t_xmax |                                raw_flags                                 | combined_flags 
--------+--------+--------+--------------------------------------------------------------------------+----------------
 (0,2)  |    864 |    865 | {HEAP_XMIN_COMMITTED,HEAP_HOT_UPDATED}                                   | {}
 (0,3)  |    865 |    866 | {HEAP_XMIN_COMMITTED,HEAP_UPDATED,HEAP_HOT_UPDATED,HEAP_ONLY_TUPLE}      | {}
 (0,3)  |    866 |    866 | {HEAP_XMAX_KEYSHR_LOCK,HEAP_XMAX_LOCK_ONLY,HEAP_UPDATED,HEAP_ONLY_TUPLE} | {}

И вот она третья версия строки появляется (xmin=xmax=866), а почему такой xmax у неё становится уже очевидным по флагу с названием HEAP_XMAX_LOCK_ONLY. Таким образом мы оставляем сведения, чтобы следующая транзакция, которая захочет обновить эту строку, должна была проверить, не запущена ли всё ещё транзакция с XID равным тому, что записано в xmax. xmax активно используется совместно с дополнительными флагами infomask в реализации блокировки строк.

→ Ссылка