Какой уровень изоляции транзакций предотвращает потерю обновления (lost update)?

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

Ответ

Для предотвращения потери обновления (lost update), когда две транзакции читают, модифицируют и перезаписывают одну и ту же строку, требуется уровень изоляции, обеспечивающий блокировки на запись.

Подходящие уровни:

  1. REPEATABLE_READ (в большинстве СУБД, кроме Oracle): На практике часто реализуется с помощью блокировок строк на запись. Если две транзакции попытаются изменить одну строку, вторая будет ждать или завершится с ошибкой.
  2. SERIALIZABLE: Гарантированно предотвращает потерю обновления за счёт строжайших блокировок или механизмов управления параллелизмом с проверкой версий (MVCC).

Пример сценария потери обновления и его предотвращение:

// Безопасный код с REPEATABLE_READ
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void incrementCounter(Long counterId) {
    Counter counter = counterRepository.findById(counterId).orElseThrow();
    // На строку 'counter' установлена блокировка для чтения (в REPEATABLE_READ),
    // которая будет повышена до блокировки на запись при сохранении.
    counter.setValue(counter.getValue() + 1);
    counterRepository.save(counter);
    // Вторая параллельная транзакция, пытающаяся изменить эту же строку,
    // будет заблокирована до коммита этой транзакции.
}

Примечание: В READ_COMMITTED потеря обновления возможна, если не используется явная блокировка (например, SELECT ... FOR UPDATE).