Опасности Read Committed

READ COMMITTED запрещает чтение незакоммиченных данных, но два оператора одной транзакции могут увидеть разные зафиксированные состояния.

Несогласованные отдельные запросы

T1: SELECT COUNT(*) FROM orders WHERE state = 'NEW'  -> 10
T2: INSERT INTO orders (...) VALUES (..., 'NEW', ...); COMMIT
T1: SELECT id FROM orders WHERE state = 'NEW'        -> 11 строк

Оба результата корректны на момент выполнения, но вместе они не образуют единый снимок.

Read-modify-write

T1 и T2 читают одно значение.
Обе рассчитывают новый результат.
Поздний UPDATE перезаписывает ранний.

Уровень не заменяет атомарный DML или контроль версии. См. Lost Update.

Проверка, затем действие

SELECT COUNT(*) FROM reservations WHERE slot_id = 7;
-- Между операторами другая транзакция вставляет строку.
INSERT INTO reservations(slot_id, user_id) VALUES (7, 12);

Если слот должен быть уникальным, правильная последняя линия защиты — UNIQUE(slot_id). Предварительный SELECT удобен для сообщения, но не обеспечивает инвариант.

Рабочие приемы

  • собрать согласованный результат одним SQL;
  • использовать constraint для инварианта;
  • выражать изменение атомарным UPDATE;
  • применять optimistic/pessimistic locking;
  • поднять изоляцию для конкретного сценария и обрабатывать конфликты.

Ловушки

  • В MVCC-СУБД снимок на READ COMMITTED часто создается для каждого оператора, но детали различаются.
  • Одна транзакция сама по себе не означает один снимок.
  • Повышение изоляции не исправляет отсутствующее бизнес-ограничение автоматически.

Связано: Грязное чтение, Repeatable read, Ограничения, constraints, Транзакции принцип ACID.

Вопросы

Что гарантирует Read Committed? Оператор не читает незакоммиченные изменения других транзакций.

Почему два SELECT могут не совпасть? Между ними фиксируются чужие изменения.

Как защитить уникальный слот? Ограничением UNIQUE, а конфликт обработать в приложении.

Нужно ли всегда переходить на Repeatable Read? Нет; часто достаточно одного запроса, constraint или условного update.