На каком уровне изоляции транзакций в PostgreSQL предотвращается фантомное чтение?

«На каком уровне изоляции транзакций в PostgreSQL предотвращается фантомное чтение?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В PostgreSQL фантомное чтение (phantom read) предотвращается только на уровне изоляции SERIALIZABLE.

Объяснение уровней изоляции в контексте фантомов:

Уровень изоляции Фантомное чтение Причина в PostgreSQL
READ UNCOMMITTED Возможно Не реализован, ведет себя как READ COMMITTED.
READ COMMITTED Возможно Видит новые коммиты других транзакций.
REPEATABLE READ Возможно Ключевой момент: В PostgreSQL REPEATABLE READ блокирует только существующие строки, удовлетворяющие условию WHERE, но не предотвращает вставку новых строк (фантомов) другими транзакциями.
SERIALIZABLE Предотвращено Использует механизм Serializable Snapshot Isolation (SSI) для обнаружения и отката транзакций, выполнение которых могло бы привести к аномалии сериализуемости, включая фантомы.

Почему в PostgreSQL REPEATABLE READ допускает фантомы? В отличие от некоторых других СУБД, PostgreSQL реализует REPEATABLE READ через Snapshot Isolation. Транзакция работает со снимком данных на момент своего начала. Она не видит новые строки, вставленные и закоммиченные после этого снимка. Однако, если такая вставка конфликтует с будущими операциями текущей транзакции (например, попытка обновить несуществующую для неё строку), конфликт не обнаруживается на уровне REPEATABLE READ. SERIALIZABLE добавляет дополнительный механизм отслеживания таких зависимостей и откатывает одну из транзакций.

Пример фантомного чтения на REPEATABLE READ:

-- Транзакция 1
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM users WHERE rating > 80; -- (1) Видит 2 пользователя

-- Транзакция 2 (выполняется параллельно)
INSERT INTO users (name, rating) VALUES ('Alice', 85);
COMMIT;

-- Транзакция 1 (продолжение)
SELECT * FROM users WHERE rating > 80; -- (2) ВСЁ ЕЩЁ видит 2 пользователя (фантома 'Alice' нет)
UPDATE users SET bonus = 100 WHERE rating > 80; -- (3) ОБНОВИТ 2 строки, но также затронет и 'Alice'!
COMMIT; -- После коммита в таблице окажутся 3 пользователя с bonus=100, включая 'Alice'.

Транзакция 1 не увидела фантома при чтении (2), но повлияла на него при обновлении (3). На уровне SERIALIZABLE такая ситуация была бы обнаружена, и одна из транзакций была бы прервана с ошибкой 40001 Serializable isolation violation.