# Serializable и SSI _MVCC и видимость · PostgreSQL Knowledge Base_ **TL;DR:** Serializable в PostgreSQL построен на снимке Repeatable Read плюс отслеживание опасных зависимостей чтения-записи между транзакциями (SSI). Транзакции не блокируются: сервер запоминает, что они читали, через предикатные блокировки SIRead, и если складывается опасный цикл, откатывает одну с ошибкой 40001. Repeatable Read замораживает снимок и убирает неповторяющиеся и фантомные чтения. Но он пропускает аномалию посложнее: две транзакции, каждая из которых читает данные, изменяемые другой. Классический пример - **перекос записи** (write skew): оба врача снимают себя с дежурства, каждый видит, что второй ещё на смене, и в итоге дежурных не остаётся. Serializable закрывает и это. В PostgreSQL он реализован как **SSI** - Serializable Snapshot Isolation. ## Не блокировать, а наблюдать SSI не заставляет транзакции ждать. Они идут на обычном снимке Repeatable Read (см. [isolation-levels](/courses/postgres/kb/isolation-levels.md)), а сервер параллельно следит за зависимостями между ними. Чтобы знать, кто что прочитал, он ставит **предикатные блокировки** - SIRead. Это не настоящие блокировки: они никого не останавливают, а лишь помечают «эта транзакция прочитала вот эти строки». ```sql -- под нагрузкой Serializable предикатные блокировки видны как SIReadLock SELECT locktype, mode FROM pg_locks WHERE mode = 'SIReadLock'; ``` ## Опасная структура и откат SSI ищет в графе зависимостей опасный узор: транзакцию, которая и читает данные, изменённые другой, и сама изменяет данные, читаемые третьей. Когда такой цикл грозит нарушить сериализуемость, сервер жертвует одной из транзакций и откатывает её: ``` ERROR: could not serialize access due to read/write dependencies among transactions ``` Код тот же `40001`, что и у конфликта записи на Repeatable Read, но причина другая: не «кто-то опередил с записью», а «совместный порядок чтений и записей нельзя свести к последовательному». Реакция приложения та же - повторить транзакцию. ## Цена и компромиссы - **Гранулярность.** Предикатные блокировки бывают на уровне строки, страницы или таблицы. Под нехваткой памяти они укрупняются, и тогда появляются ложные срабатывания - лишние откаты там, где реального конфликта не было. - **Короткие транзакции.** Чем дольше живёт транзакция, тем больше она накопит зависимостей и тем выше шанс попасть под откат. - **Готовность повторять.** Serializable снимает с разработчика ручные блокировки `SELECT FOR UPDATE`, но взамен требует цикла повтора по `40001`. SSI - это сделка: пишешь код так, будто транзакции выполняются строго по очереди, а движок сам ловит случаи, где параллельность это нарушила бы, и заставляет повторить. ## Команды ```sql BEGIN ISOLATION LEVEL SERIALIZABLE; ``` Открыть транзакцию с гарантией сериализуемости через SSI ```sql SELECT locktype, mode FROM pg_locks WHERE mode = 'SIReadLock'; ``` Предикатные блокировки, которыми SSI отмечает прочитанное ```sql SHOW max_pred_locks_per_transaction; ``` Лимит предикатных блокировок до укрупнения гранулярности ## См. также - [Уровни изоляции в PostgreSQL](/courses/postgres/kb/isolation-levels.md) - [Снимок данных (snapshot)](/courses/postgres/kb/snapshot.md) - [xmin, xmax и правила видимости](/courses/postgres/kb/xmin-xmax.md)