Что такое MVCC в PostgreSQL
Что проверяют
Понимаете ли вы, что читатели в PostgreSQL не блокируют писателей не по волшебству, а потому что старые версии строк физически остаются в таблице. За вопросом обычно идёт продолжение про раздувание, VACUUM и про то, как сервис на Go своими долгими транзакциями мешает уборке.
Как отвечать
Каждая версия строки хранит служебные номера транзакций, создавшей её и удалившей. UPDATE не правит строку на месте: он пишет новую версию и помечает старую как удалённую. Транзакция получает снимок, то есть набор правил о том, какие версии ей видны, и работает с ним.
Отсюда выводы:
- Чтение не ждёт записи, а запись не ждёт чтения. Две записи в одну строку всё равно выстраиваются в очередь: вторая ждёт фиксации первой.
- На уровне READ COMMITTED, который стоит по умолчанию, снимок берётся заново на каждый оператор. Снимок на всю транзакцию это уже REPEATABLE READ.
- Мёртвые версии убирает VACUUM. Без него растут и таблица, и индексы, а сканирование замедляется.
На уровнях выше READ COMMITTED запись может не пройти из-за конфликта сериализации: код обязан повторить транзакцию, а не падать с ошибкой.
Где спотыкаются
Говорят, что транзакция всегда видит согласованный снимок на момент старта. По умолчанию это не так: два одинаковых запроса внутри одной транзакции вернут разное, если между ними кто-то зафиксировал изменения.
Слышат в MVCC «блокировок нет вообще». Обновление той же строки, явные блокировки, изменение схемы и ожидание в очереди никуда не делись; MVCC убирает только конфликт чтения с записью.
Держат транзакцию открытой, пока ходят в сторонний сервис, или забывают откат на ветке с ошибкой. Соединение возвращается в пул с живой транзакцией, её номер держит горизонт видимости, и уборка не может убрать ничего новее этого номера.