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скоро
  • Уроки
  • База знаний
  • Собеседование
Часть II — Многоверсионность (MVCC)

$ глава 10 · 55 минут

Снимок данных: числа и правила видимости

В прошлой главе сессия 2 «не видела» незакоммиченных изменений сессии 1. Откуда она знает, что видеть, а что нет? Из снимка. Слово звучит так, будто база делает фотографию всех данных на момент старта транзакции. Это заблуждение, и в этой главе мы его развенчаем.

Снимок не копирует ни байта. Это несколько чисел плюс правило, по которому из этих чисел и полей xmin/xmax строки выводится её видимость. Мы посмотрим на снимок прямо в SQL и научимся читать его три числа.

10.1 Снимок - это не копия

Частое заблуждение: «снимок - это фотография таблицы на момент старта транзакции». Если бы это было так, каждая транзакция копировала бы базу целиком - на больших данных немыслимо дорого.

На деле снимок - это компактный набор чисел. По ним и по версионным полям каждой строки (xmin/xmax, см. tuple-header) транзакция вычисляет на лету, видеть ей конкретную версию или нет. Ничего не копируется: видимость не хранится, а считается. Полный разбор - в snapshot.

10.2 Три числа

Посмотрим на снимок своими глазами:

sql
SELECT pg_current_snapshot();
-- 787:787:

Формат - xmin:xmax:список. Три части:

  • xmin снимка - граница, ниже которой все транзакции уже решены (зафиксированы или откатились);
  • xmax снимка - граница, начиная с которой транзакций ещё не было на момент снимка;
  • список - идентификаторы транзакций между xmin и xmax, которые шли прямо в момент снимка.

В примере оба числа равны и список пуст - рядом никого. Под нагрузкой это выглядело бы как 100:105:101,103: три активных границы и две транзакции в работе.

10.3 Три границы времени

Снимок делит все транзакции на три зоны относительно момента, когда он взят:

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

10.4 Правило видимости через снимок

Теперь соберём снимок и поля строки вместе. Версия видна, если её xmin:

  • меньше xmin снимка - точно в прошлом, и если та транзакция закоммитилась, версия годится; либо
  • между xmin и xmax снимка, не в списке активных, и зафиксирован по clog.

И при этом xmax версии не должен быть «уже зафиксированным в прошлом» - иначе версия удалена с точки зрения снимка. Полные правила для пары полей - в xmin-xmax. Главное: одни и те же версии на диске разные транзакции видят по-разному, потому что у каждой свой снимок, а правило одно на всех.

10.5 Снимок можно передать

Раз снимок - это просто числа, его можно отдать другой сессии, чтобы обе видели данные в одинаковом срезе времени.

sql
-- сессия 1, внутри открытой транзакции
SELECT pg_export_snapshot();
-- 00000006-00000002-1
-- сессия 2
BEGIN ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION SNAPSHOT '00000006-00000002-1';
-- теперь сессия 2 видит данные так же, как сессия 1

Так работает параллельный pg_dump: ведущий процесс экспортирует снимок, рабочие подхватывают его и выгружают разные таблицы из одного среза времени, получая согласованную копию. Детали и условия - в snapshot-export.

10.6 Когда берётся снимок

Момент взятия снимка зависит от уровня изоляции. На уровне по умолчанию (Read Committed) новый снимок берётся на каждый оператор - поэтому соседний SELECT в той же транзакции может увидеть свежие чужие коммиты. На Repeatable Read и Serializable снимок берётся один раз на транзакцию и держится до конца: сколько ни перечитывай, картина та же.

Это прямое продолжение опыта из прошлой главы и тема следующей - isolation-levels. Запомни связь: уровень изоляции - это, по сути, политика «когда обновлять снимок».

10.7 Подводный камень: цена снимка растёт с соединениями

Снимок дёшев по памяти - это несколько чисел. Но построить его не бесплатно: чтобы узнать список активных транзакций, сервер обходит структуру со всеми работающими процессами. Чем больше открытых соединений, тем дороже взять снимок, а берётся он часто - как минимум на каждую транзакцию, а на Read Committed на каждый оператор.

Отсюда неочевидное следствие: тысячи почти простаивающих соединений вредят, даже когда «ничего не делают». Каждое из них утяжеляет снимок всем остальным. Это ещё один довод за умеренный пул соединений, который мы упоминали в части про процессы. Связь прямая: много соединений - дорогие снимки - медленнее вся система.

Уроки в sandbox

lab-10.1. Снимок своими глазами

Посмотри на снимок в SQL и убедись, что на Repeatable Read он заморожен: чужой коммит, случившийся после старта транзакции, в неё не просочится. Перед каждым чтением предсказывай результат.

  1. В сессии A посмотри текущий снимок: SELECT pg_current_snapshot(); - разбери его на три части xmin:xmax:список.

  2. В сессии A открой замороженную транзакцию: BEGIN ISOLATION LEVEL REPEATABLE READ; и прочитай число строк: SELECT count(*) FROM tickets;. Запомни его.

  3. В сессии B добавь строку и зафиксируй: INSERT INTO tickets VALUES ('9999999999999','000001',1,'NEW'); COMMIT;.

  4. Предскажи: что покажет повторный SELECT count(*) FROM tickets; в сессии A - старое число или новое?

  5. Прочитай в сессии A ещё раз - число прежнее: снимок заморожен на старте транзакции.

  6. Заверши транзакцию A (COMMIT) и прочитай снова - теперь видна вставка сессии B.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • Снимок - не копия данных, а числа xmin, xmax и список активных xid плюс правило видимости.
  • pg_current_snapshot() показывает снимок в формате xmin:xmax:список.
  • Снимок делит транзакции на прошлое (ниже xmin), настоящее (в списке) и будущее (от xmax).
  • Видимость не хранится, а вычисляется из снимка и полей xmin/xmax строки; правило одно на всех.
  • Снимок можно передать другой сессии через pg_export_snapshot и SET TRANSACTION SNAPSHOT.
  • Когда берётся снимок, определяет уровень изоляции: на каждый оператор или один раз на транзакцию.
  • Построение снимка дорожает с числом соединений - ещё довод за пул соединений.

Контрольные вопросы

  1. Почему снимок - это не копия данных?

    Показать ответ

    Потому что он не копирует ни байта. Снимок - это несколько чисел (xmin, xmax, список активных xid) и правило, по которому из них и версионных полей строки вычисляется видимость на лету. Если бы снимок был копией, каждая транзакция дублировала бы базу - немыслимо дорого. Вместо этого видимость не хранится, а считается из компактного набора чисел.

  2. Что означают три части снимка xmin:xmax:список?

    Показать ответ

    xmin снимка - граница, ниже которой все транзакции уже решены (зафиксированы или откатились). xmax снимка - граница, начиная с которой транзакций на момент снимка ещё не существовало. Список - идентификаторы транзакций между этими границами, которые шли прямо в момент снимка. Вместе они делят все транзакции на прошлое, настоящее и будущее.

  3. Как уровень изоляции связан со снимком?

    Показать ответ

    Уровень изоляции определяет, когда берётся снимок. На Read Committed новый снимок берётся на каждый оператор, поэтому соседние запросы в одной транзакции могут увидеть свежие чужие коммиты. На Repeatable Read и Serializable снимок берётся один раз в начале транзакции и держится до конца, поэтому повторное чтение даёт ту же картину. По сути уровень изоляции - это политика обновления снимка.

  4. Зачем нужен экспорт снимка между сессиями?

    Показать ответ

    Чтобы две сессии видели данные в одинаковом срезе времени. Одна сессия вызывает pg_export_snapshot и получает идентификатор, другая первым делом выполняет SET TRANSACTION SNAPSHOT с этим идентификатором. Главный потребитель - параллельный pg_dump: рабочие процессы выгружают разные таблицы из одного снимка, получая согласованную копию базы.

  5. Почему много открытых соединений замедляют работу даже в простое?

    Показать ответ

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

← Предыдущая09-why-mvccСледующая →11-isolation-levels
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.