kb/mvcc
Многоверсионность как структура данных: xmin/xmax, снимок как набор чисел и правил видимости, уровни изоляции, clog и hint bits. Здесь становится понятно, почему UPDATE — это DELETE плюс INSERT.
Зафиксирована транзакция или откатилась, хранит clog (каталог pg_xact) - всего два бита на транзакцию. Чтобы не дёргать clog при каждом чтении строки, результат проверки кэшируется в самой строке подсказками фиксации в t_infomask. Первый прочитавший строку ставит подсказку - и тем самым пачкает страницу, даже если это был обычный SELECT.
Serializable в PostgreSQL построен на снимке Repeatable Read плюс отслеживание опасных зависимостей чтения-записи между транзакциями (SSI). Транзакции не блокируются: сервер запоминает, что они читали, через предикатные блокировки SIRead, и если складывается опасный цикл, откатывает одну с ошибкой 40001.
У каждой версии строки два транзакционных штампа: xmin (кто вставил) и xmax (кто пометил устаревшей, 0 - если жива). Версия видна транзакции, если её xmin уже зафиксирован и попадает в снимок, а xmax либо пуст, либо ещё не зафиксирован, либо не виден снимку. По этим двум числам строится вся видимость.
Каждая транзакция сразу получает виртуальный xid - дешёвую пару «бэкенд/счётчик», которая ничего не расходует. Настоящий 32-битный xid выдаётся только когда транзакция впервые что-то пишет. Поэтому read-only транзакции не тратят xid вовсе - это бережёт ограниченное пространство счётчика и отдаляет wraparound.
SAVEPOINT открывает вложенную транзакцию (подтранзакцию) внутри основной. ROLLBACK TO SAVEPOINT откатывает её работу, не трогая остальную транзакцию. Каждая пишущая подтранзакция получает свой xid, а связь «подтранзакция - родитель» хранит pg_subtrans. Блоки EXCEPTION в PL/pgSQL - это тоже подтранзакции.
PostgreSQL не меняет строку на месте. UPDATE пишет новую версию рядом и помечает старую как устаревшую, DELETE только помечает. Поэтому читающие транзакции не ждут пишущих, а пишущие не ждут читающих: каждый видит свою версию. Плата за это - мёртвые версии, которые потом убирает vacuum.
Снимок - это не копия данных, а три числа: xmin, xmax и список активных xid между ними. Плюс правило, как по ним решать видимость версии. По снимку транзакция отделяет «прошлое» (зафиксировано до неё) от «настоящего» (идёт прямо сейчас) и «будущего» (ещё не начато). Дёшево по памяти, дорого по числу соединений.
PostgreSQL различает три уровня изоляции. Read Committed (по умолчанию) берёт свежий снимок на каждый оператор. Repeatable Read берёт один снимок на транзакцию и держит его до конца. Serializable добавляет проверку, что транзакции можно выстроить в строгий порядок. Грязного чтения в PostgreSQL не бывает ни на одном уровне.
pg_export_snapshot() возвращает идентификатор снимка, а другая сессия через SET TRANSACTION SNAPSHOT берёт ровно тот же снимок и видит данные в том же срезе времени. Так работает параллельный pg_dump: все рабочие процессы выгружают согласованную копию. Экспортирующая транзакция должна оставаться открытой.