Изоляция транзакций

Изоляция транзакций — фундаментальный механизм PostgreSQL, обеспечивающий согласованность данных при параллельном выполнении операций.
В основе лежит MVCC (Multiversion Concurrency Control) — многоверсионное управление параллелизмом.

Почему это важно?
  • Гарантирует целостность данных при конкурентном доступе

  • Позволяет избегать блокировок в большинстве сценариев

  • Обеспечивает гибкость через уровни изоляции


Уровни изоляции ANSI/SQL

PostgreSQL поддерживает 4 уровня (от строгого к мягкому):

Уровень Описание Аномалии
Serializable Полная изоляция (как последовательное выполнение) Нет
Repeatable Read Гарантирует непротиворечивость "снимка" данных Фантомные чтения
Read Committed Видны только зафиксированные данные (уровень по умолчанию) Неповторяемые чтения, фантомы
Read Uncommitted Видны даже незафиксированные изменения (в PostgreSQL работает как Read Committed) Все

Реализация MVCC в PostgreSQL

  • Каждая строка хранит xmin (версия создания) и xmax (версия удаления)

  • Snapshot Isolation: транзакция видит только данные, зафиксированные до её начала

  • VACUUM: очищает "мертвые" версии строк

-- Просмотр метаданных версий SELECT xmin, xmax, * FROM users WHERE id = 1;
Конфликты и блокировки
  • Lost Update: перезапись изменений (решается через SELECT FOR UPDATE)

  • Deadlock: взаимная блокировка (PostgreSQL автоматически обнаруживает)

  • Serialization Failure: при уровне Serializable


Настройка уровня изоляции
-- Глобальная настройка (postgresql.conf) default_transaction_isolation = 'repeatable read' -- Для конкретной транзакции BEGIN ISOLATION LEVEL SERIALIZABLE; -- Операции... COMMIT;

Практические примеры

Задание 1: Демонстрация аномалий
Неповторяемое чтение (Read Committed):

Если одна транзакция дважды читает одни и те же данные, но между этими чтениями другая транзакция изменила и зафиксировала эти данные, то первая транзакция увидит разные значения.

-- Сессия 1 BEGIN; SELECT * FROM accounts WHERE user_id = 1; -- Чтение 1 -- Сессия 2 UPDATE accounts SET balance = 100 WHERE user_id = 1; COMMIT; -- Сессия 1 SELECT * FROM accounts WHERE user_id = 1; -- Чтение 2 (данные изменились!) COMMIT;

В первой сессии после второго чтения данные изменились, хотя транзакция ещё не завершилась.


Фантомные чтения (Repeatable Read):

Рассмотрим пример, который демонстрирует ключевую аномалию уровня изоляции Repeatable Read, которая проявляется, когда в рамках одной транзакции появляются новые строки, добавленные другими транзакциями.

  • Транзакция видит "снимок" (snapshot) данных на момент своего начала.

  • Она не видит изменения существующих строк (защита от неповторяемых чтений).

  • Но если другая транзакция добавит новые строки (удовлетворяющие условию запроса), они внезапно "появятся" — это и есть фантомные чтения.

-- Сессия 1 (уровень Repeatable Read) BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT COUNT(*) FROM orders; -- Результат: 100 записей -- Сессия 2 (параллельно) INSERT INTO orders VALUES (101, 'new_order'); -- Добавляет новую запись COMMIT; -- Сессия 1 (продолжение) SELECT COUNT(*) FROM orders; -- Результат: всё ещё 100 (!) COMMIT;
  1. Первый SELECT в Сессии 1 зафиксировал состояние таблицы (count = 100).

  2. Сессия 2 добавила новую запись и зафиксировала её.

  3. Повторный SELECT в Сессии 1 не увидел новую запись, потому что Repeatable Read сохраняет исходный снимок данных.

Когда это критично?

Примеры опасных сценариев:

  1. Финансовые отчёты:

    • Транзакция рассчитывает итоговую сумму по всем платежам.

    • Параллельно добавляется новый платёж → расчёт не учитывает его, но деньги уже списаны.

  2. Резервирование мест:

    • Система проверяет количество свободных мест.

    • Параллельно кто-то бронирует место → возникает двойное бронирование.

Как избежать фантомных чтений?
Решение 1: Использовать Serializable
BEGIN ISOLATION LEVEL SERIALIZABLE; -- Полная изоляция SELECT COUNT(*) FROM orders; -- Теперь фантомы невозможны COMMIT;

PostgreSQL использует Serializable Snapshot Isolation (SSI) для блокировки потенциальных конфликтов.

Решение 2: Явные блокировки
BEGIN ISOLATION LEVEL REPEATABLE READ; -- Блокировка ВСЕХ строк, удовлетворяющих условию (даже будущих!) SELECT * FROM orders WHERE status = 'new' FOR UPDATE; -- Теперь другие транзакции не смогут добавить строки с status='new' COMMIT;

Сравнение уровней изоляции

Уровень Фантомные чтения Неповторяемые чтения Грязные чтения
Read Committed ❌ (возможны) ✅ (запрещено)
Repeatable Read ❌ (в PostgreSQL их нет!) ✅ (запрещены)
Serializable ✅ (запрещены)

Комментарии

Добавить комментарий

Чтобы оставить комменатрий необходимо Авторизоваться