linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it worksскоро
  • Уроки
  • База знаний
  • Собеседование
home/postgres/kb/MVCC и видимость/why-mvcc

kb/mvcc ── MVCC и видимость ── beginner

Зачем многоверсионность

PostgreSQL не меняет строку на месте. UPDATE пишет новую версию рядом и помечает старую как устаревшую, DELETE только помечает. Поэтому читающие транзакции не ждут пишущих, а пишущие не ждут читающих: каждый видит свою версию. Плата за это - мёртвые версии, которые потом убирает vacuum.

view as markdownaka: mvcc-intro, multiversion

Представь банковскую таблицу. Один запрос считает общий баланс по всем счетам - читает миллион строк. В это же время идёт перевод денег между двумя счетами. Если перевод поменяет строки прямо под носом у отчёта, отчёт получит несогласованную картину: деньги уже списаны с одного счёта, но ещё не зачислены на другой.

Старое решение - блокировки: пишущий запирает строку, читающие ждут. Просто, но медленно: отчёт на миллион строк остановит все переводы. PostgreSQL идёт другим путём - многоверсионностью (MVCC, Multi-Version Concurrency Control).

Главная идея: не менять, а добавлять

Строка не правится на месте. Вместо этого:

ОперацияЧто физически происходит
INSERTпоявляется новая версия строки
UPDATEстарая версия помечается устаревшей, рядом пишется новая
DELETEверсия помечается устаревшей, ничего не пишется

Каждая версия помнит, какая транзакция её создала (xmin) и какая пометила устаревшей (xmax). По этим числам любая транзакция решает, видеть ей эту версию или нет. Правила разбирает xmin-xmax, а сами поля лежат в tuple-header.

UPDATE - это не изменение, а DELETE плюс INSERT

Проще всего увидеть это на системных колонках:

sql
CREATE TABLE mv (id int, note text);
INSERT INTO mv VALUES (1, 'a');
SELECT xmin, xmax, ctid FROM mv;   -- xmin=786, xmax=0, ctid=(0,1)
UPDATE mv SET note = 'b' WHERE id = 1;
SELECT xmin, xmax, ctid FROM mv;   -- новая версия: другой xmin, ctid=(0,2)

SELECT всегда показывает ту версию, что видна твоей транзакции. После UPDATE это новая версия с новым ctid. Старая никуда не делась - она лежит рядом и видна транзакциям, которые начались до коммита обновления.

Читатели и писатели не мешают друг другу

Из этой схемы следует главное свойство: читающий запрос никогда не блокирует пишущий и наоборот. Отчёт по балансу видит версии, какими они были на момент его старта (его снимок - см. snapshot). Перевод денег тем временем спокойно пишет новые версии. Никто никого не ждёт.

Цена: мусор и vacuum

Бесплатно это не даётся. Каждый UPDATE и DELETE оставляет мёртвые версии, которые занимают место, пока их видит хоть одна транзакция. Когда последняя такая транзакция завершится, версии становятся мусором. Убирает его vacuum (см. vacuum). Поэтому активно изменяемая таблица в PostgreSQL всегда чуть больше, чем «чистый» объём данных, и требует регулярной очистки. Это не баг, а обратная сторона того, что никто никого не ждёт.

§ команды

bash
SELECT xmin, xmax, ctid, * FROM mv;

Системные колонки версии: кто создал, кто пометил устаревшей, адрес

bash
SELECT n_live_tup, n_dead_tup FROM pg_stat_all_tables WHERE relname = 'mv';

Сколько живых и мёртвых версий накопила таблица

bash
UPDATE mv SET note = note WHERE id = 1; SELECT ctid FROM mv WHERE id = 1;

Даже пустое по смыслу обновление создаёт новую версию с новым ctid

§ см. также

  • xmin-xmaxxmin, xmax и правила видимостиУ каждой версии строки два транзакционных штампа: xmin (кто вставил) и xmax (кто пометил устаревшей, 0 - если жива). Версия видна транзакции, если её xmin уже зафиксирован и попадает в снимок, а xmax либо пуст, либо ещё не зафиксирован, либо не виден снимку. По этим двум числам строится вся видимость.
  • snapshotСнимок данных (snapshot)Снимок - это не копия данных, а три числа: xmin, xmax и список активных xid между ними. Плюс правило, как по ним решать видимость версии. По снимку транзакция отделяет «прошлое» (зафиксировано до неё) от «настоящего» (идёт прямо сейчас) и «будущего» (ещё не начато). Дёшево по памяти, дорого по числу соединений.
  • tuple-headerЗаголовок кортежаПеред данными каждой строки лежит служебный заголовок в 23 байта, выровненный до 24. В нём t_xmin и t_xmax (кто вставил и кто удалил версию), t_ctid (ссылка на себя или на новую версию), t_infomask с флагами и t_hoff - смещение до пользовательских данных. Это поля, на которых стоит весь MVCC.
  • isolation-levelsУровни изоляции в PostgreSQLPostgreSQL различает три уровня изоляции. Read Committed (по умолчанию) берёт свежий снимок на каждый оператор. Repeatable Read берёт один снимок на транзакцию и держит его до конца. Serializable добавляет проверку, что транзакции можно выстроить в строгий порядок. Грязного чтения в PostgreSQL не бывает ни на одном уровне.
  • vacuumVACUUM и removable cutoffVACUUM удаляет мёртвые версии из таблицы и индексов, обновляет Free Space Map и Visibility Map, продвигает горизонт заморозки. Удаляет только версии старше removable cutoff. Обычный VACUUM не уменьшает файл - это делает VACUUM FULL под эксклюзивной блокировкой.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки