MVCC в PostgreSQL: как устроены версии строк и зачем нужен VACUUM?
PostgreSQL не меняет строки на месте: UPDATE пишет новую версию строки и помечает старую как удалённую в этой транзакции, DELETE только помечает. Каждая версия несёт xmin (какая транзакция создала) и xmax (какая удалила), а транзакция видит те версии, которые были зафиксированы до её снимка. Отсюда три следствия. Первое — читатели не блокируют писателей и наоборот, что и есть главный выигрыш модели. Второе — мёртвые версии накапливаются, занимают место в таблице и индексах, и без уборки таблица «раздувается», а запросы читают лишние страницы. Третье — уборкой занимается VACUUM, который помечает место как свободное для повторного использования, обновляет карту видимости для index-only scan и «замораживает» старые версии, защищая от переполнения счётчика транзакций. VACUUM FULL — другое: он переписывает таблицу целиком под эксклюзивной блокировкой.