# Снимок данных (snapshot) _MVCC и видимость · PostgreSQL Knowledge Base_ **TL;DR:** Снимок - это не копия данных, а три числа: xmin, xmax и список активных xid между ними. Плюс правило, как по ним решать видимость версии. По снимку транзакция отделяет «прошлое» (зафиксировано до неё) от «настоящего» (идёт прямо сейчас) и «будущего» (ещё не начато). Дёшево по памяти, дорого по числу соединений. Частое заблуждение: «снимок - это фотография таблицы на момент старта». На деле снимок не копирует ни байта данных. Это компактный набор чисел, по которому транзакция вычисляет, какие версии строк ей видны. Посмотреть текущий снимок: ```sql SELECT pg_current_snapshot(); -- 787:787: ``` Формат - `xmin:xmax:список`. Здесь оба числа равны и список пуст, значит активных транзакций рядом нет. Под нагрузкой это выглядело бы как `100:105:101,103` - три числа и больше ничего. ## Три границы времени | Часть снимка | Смысл | |---|---| | `xmin` | ниже этого xid всё уже решено (зафиксировано или откатилось) | | `xmax` | этот xid и выше ещё не существовали на момент снимка | | список | xid между xmin и xmax, что шли прямо в момент снимка | Снимок делит транзакции на три зоны. Всё **ниже xmin** - прошлое: результат известен. Всё **в списке** - настоящее: идёт сейчас, результат ещё не виден. Всё **выше или равно xmax** - будущее: на момент снимка не начиналось. ## Правило видимости через снимок Версия строки видна, если её `xmin`: - меньше `xmin` снимка - точно в прошлом, и если транзакция коммитнулась, версия видна; либо - между `xmin` и `xmax`, не в списке активных, и зафиксирован по clog. И при этом `xmax` версии не должен быть «уже зафиксированным в прошлом» - иначе версия удалена с точки зрения снимка. Полные правила для пары полей - в [xmin-xmax](/courses/postgres/kb/xmin-xmax.md). ## Когда снимок берётся Момент взятия снимка зависит от уровня изоляции (см. [isolation-levels](/courses/postgres/kb/isolation-levels.md)). На Read Committed новый снимок берётся **на каждый оператор**: соседний SELECT в той же транзакции может увидеть свежие чужие коммиты. На Repeatable Read и Serializable снимок берётся **один раз на транзакцию** и держится до конца - поэтому повторное чтение даёт тот же результат. Снимок можно даже передать в другую сессию, чтобы две транзакции видели ровно одно и то же. Это [snapshot-export](/courses/postgres/kb/snapshot-export.md). ## Подводный камень: цена снимка растёт с числом соединений Снимок дёшев по памяти, но не бесплатен по построению. Чтобы узнать список активных транзакций, сервер обходит структуру со всеми работающими бэкендами. Чем больше открытых соединений, тем дороже взять снимок - а берётся он часто. Поэтому тысячи почти простаивающих соединений вредят, даже когда «ничего не делают»: каждый из них утяжеляет снимок всем остальным. Отсюда правило держать пул соединений умеренным. А долгая транзакция, которая держит снимок открытым, заодно отодвигает горизонт уборки мусора (см. [transaction-horizon](/courses/postgres/kb/transaction-horizon.md)). ## Команды ```sql SELECT pg_current_snapshot(); ``` Текущий снимок в формате xmin:xmax:список активных xid ```sql SELECT pg_snapshot_xmin(pg_current_snapshot()), pg_snapshot_xmax(pg_current_snapshot()); ``` Разобрать снимок на границы прошлого и будущего ```sql SELECT pg_current_xact_id_if_assigned(); ``` Реальный xid текущей транзакции (пусто, если она ещё ничего не писала) ## См. также - [xmin, xmax и правила видимости](/courses/postgres/kb/xmin-xmax.md) - [Уровни изоляции в PostgreSQL](/courses/postgres/kb/isolation-levels.md) - [Экспорт снимка между сессиями](/courses/postgres/kb/snapshot-export.md) - [clog и подсказки фиксации (hint bits)](/courses/postgres/kb/clog-hint-bits.md) - [Горизонт транзакции](/courses/postgres/kb/transaction-horizon.md)