Частое заблуждение: «снимок - это фотография таблицы на момент старта». На деле снимок не копирует ни байта данных. Это компактный набор чисел, по которому транзакция вычисляет, какие версии строк ей видны.
Посмотреть текущий снимок:
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.
Когда снимок берётся
Момент взятия снимка зависит от уровня изоляции (см. isolation-levels). На Read Committed новый снимок берётся на каждый оператор: соседний SELECT в той же транзакции может увидеть свежие чужие коммиты. На Repeatable Read и Serializable снимок берётся один раз на транзакцию и держится до конца - поэтому повторное чтение даёт тот же результат.
Снимок можно даже передать в другую сессию, чтобы две транзакции видели ровно одно и то же. Это snapshot-export.
Подводный камень: цена снимка растёт с числом соединений
Снимок дёшев по памяти, но не бесплатен по построению. Чтобы узнать список активных транзакций, сервер обходит структуру со всеми работающими бэкендами. Чем больше открытых соединений, тем дороже взять снимок - а берётся он часто. Поэтому тысячи почти простаивающих соединений вредят, даже когда «ничего не делают»: каждый из них утяжеляет снимок всем остальным. Отсюда правило держать пул соединений умеренным. А долгая транзакция, которая держит снимок открытым, заодно отодвигает горизонт уборки мусора (см. transaction-horizon).