awesome-everything EN
↑ Обратно к восхождению

Базы данных

Заголовок tuple и механика снимков

Суть Каждый tuple хранит 23-байтный заголовок с t_xmin/t_xmax/t_ctid; snapshot закрепляет xmin/xmax/xip; правило видимости применяется построчно без read-блокировок.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 16 min

Postgres читает три числа из заголовка каждой строки, сравнивает их с тремя числами в snapshot транзакции и принимает решение о видимости — без единой read-блокировки. Разберём механику побайтово.

Заголовок tuple

Когда Postgres сохраняет tuple на heap-странице, перед пользовательскими колонками идёт 23-байтный заголовок:

ПолеТипНазначение
t_xmin32-bit XIDТранзакция, вставившая эту версию
t_xmax32-bit XIDТранзакция, удалившая/обновившая версию (0 = живая)
t_cid32-bitCommand id внутри транзакции
t_ctid(block, offset)Указатель на следующую версию строки
t_infomask16-bitБиты статуса: XMIN_COMMITTED, XMAX_INVALID, HOT-флаги и др.
t_infomask216-bitЧисло атрибутов + флаги HOT

Именно эти поля решают вопрос видимости на каждой строке, которую Postgres смотрит.

Как snapshot решает, что читать

Когда транзакция начинается — при первом запросе под READ COMMITTED, или на BEGIN под REPEATABLE READ — Postgres строит snapshot. Это небольшая структура, фиксирующая три числа:

  • xmin — id самой старой ещё работающей транзакции
  • xmax — id на единицу больше последнего закоммиченного на момент snapshot
  • xip — список незавершённых транзакций между xmin и xmax

Правило видимости (применяется построчно):

Tuple виден, если:

  • его t_xmin закоммичен и не в списке xip, И
  • его t_xmax равен нулю, ИЛИ откатан, ИЛИ в списке xip

Никаких read-блокировок не задействовано — это и есть весь механизм.

INSERT, UPDATE, DELETE — что реально происходит на диске

INSERT — кладёт свежий tuple с t_xmin = id текущей транзакции, t_xmax = 0.

UPDATE — две операции:

  1. Помечает старый tuple: t_xmax = текущая транзакция
  2. Вставляет новый tuple: t_xmin = та же транзакция; t_ctid старого указывает на адрес нового

DELETE — помечает существующий tuple: t_xmax = текущая транзакция. Ничего нового не пишет.

Ни одна из операций не удаляет ничего физически. Все логически обратимы до коммита, а после коммита по-прежнему физически на месте, пока VACUUM не решит, что они больше никому не нужны.

Пример: параллельный UPDATE и SELECT

Проследи одну строку через UPDATE и параллельный SELECT

1/3

Проверь себя

Проследи
1/6

Проследи MVCC-состояние одной строки под SELECT, потом UPDATE, потом DELETE в трёх разных транзакциях, плюс четвёртая долгая SELECT держит старый snapshot.

1
Step 1 of 6
T1 вставляет (id=5, balance=100). T1 коммитит. Состояние heap?
2
Locked
T2 начинается, читает id=5 и остаётся открытой без коммита. Тем временем T3 начинается.
3
Locked
T3 выполняет UPDATE accounts SET balance = 50 WHERE id = 5, потом коммитит.
4
Locked
T4 начинается, выполняет DELETE FROM accounts WHERE id = 5, коммитит.
5
Locked
Что показывает pg_stat_all_tables.n_dead_tup для этой таблицы?
6
Locked
T2 наконец-то коммитит. Что произойдёт?
Викторина

На каком уровне изоляции Postgres обнаруживает lost update и бросает SQLSTATE 40001 вместо тихой перезаписи?

Викторина

UPDATE ставит t_xmax строки в id текущей транзакции и вставляет новый tuple. Что происходит со старым tuple сразу после коммита?

Вспомните перед уходом
  1. 01
    Назови три поля snapshot в Postgres и объясни роль каждого.
  2. 02
    Почему t_ctid важен для операции UPDATE, но не для DELETE?
  3. 03
    Долгая транзакция T2 стартует, ничего не делает, и держится открытой. T3 делает 10 000 UPDATE на таблице orders и коммитит. Почему VACUUM всё равно не может убрать мёртвые tuple?
Recap
  • Заголовок tuple: 23 байта с t_xmin, t_xmax, t_ctid, t_infomask
  • Snapshot: три числа (xmin, xmax, xip); правило видимости применяется построчно без блокировок
  • INSERT: один новый tuple. UPDATE: два tuple (старый с t_xmax, новый с t_xmin). DELETE: один tuple с t_xmax
  • Физического удаления нет; VACUUM помечает мёртвые tuple переиспользуемыми после того как ни один snapshot их не нуждается
  • Под RC snapshot обновляется на каждом statement; под RR — берётся один раз на BEGIN
Связанные уроки
встречается в140
Продолжить восхождение ↑HOT-обновления и уровни изоляции
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.