Lost Update

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

Начальное quantity = 10
T1 читает 10             T2 читает 10
T1 пишет 11, commit      T2 пишет 11, commit
Итог 11, хотя ожидался 12

Решение 1: атомарный UPDATE

Если изменение можно выразить относительно текущего значения, чтение приложению не нужно:

UPDATE inventory
SET quantity = quantity + 1
WHERE sku = 'A-1';

СУБД сериализует конфликтующие обновления строки своим механизмом блокировок/версий.

Решение 2: оптимистическая версия

UPDATE orders
SET state = 'PAID',
    version = version + 1
WHERE id = 42
  AND version = 7
  AND state = 'NEW';

Если обновлено 0 строк, состояние изменилось конкурентно. Приложение решает: перечитать, вернуть конфликт или безопасно повторить.

Решение 3: пессимистическая блокировка

BEGIN;
SELECT state
FROM orders
WHERE id = 42
FOR UPDATE;
 
UPDATE orders SET state = 'PAID' WHERE id = 42;
COMMIT;

Блокировка нужна до чтения, на котором основано решение. Она повышает ожидания и риск deadlock, поэтому порядок захвата ресурсов должен быть стабильным.

Решение 4: constraint или serializable

Часть инвариантов надежнее выразить ограничением. Для сложных предикатных инвариантов может понадобиться SERIALIZABLE с обработкой serialization failure.

Ловушки

  • synchronized защищает только один процесс приложения.
  • Проверка версии без анализа количества измененных строк бесполезна.
  • Слепой retry может повторно выполнить внешний побочный эффект.
  • Высокий уровень изоляции не следует считать универсальной защитой: поведение lost update зависит от реализации.

Связано: Изоляция транзакций, Транзакции принцип ACID, Кейс исключение было проглочено внутри транзакции.

Вопросы

Самое простое решение для счетчика? Атомарный UPDATE counter = counter + ?.

Когда выбирать optimistic locking? Когда конфликты редки и блокировать строку заранее дорого.

Как обнаружить конфликт версии? Проверить, что условный UPDATE изменил ровно одну строку.

Почему локальный lock приложения недостаточен? Есть другие инстансы и внешние клиенты БД.