Значения поля 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 в реализации блокировки строк.