Что происходит в базе данных при одновременном обновлении одной строки двумя транзакциями?

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

Ответ

Поведение зависит от уровня изоляции транзакций и механизма блокировок СУБД. Обычно происходит следующее:

  1. Блокировка строки: Первая транзакция, начавшая обновление, устанавливает на строку эксклюзивную блокировку (X-lock).
  2. Ожидание или отказ: Вторая транзакция, пытающаяся обновить ту же строку:
    • Блокируется (ожидает снятия блокировки первой транзакцией) при уровнях изоляции READ COMMITTED, REPEATABLE READ или SERIALIZABLE.
    • Может завершиться ошибкой (например, SQLSTATE 40001 — deadlock) или таймаутом, если СУБД обнаружит взаимоблокировку (deadlock).
  3. Последовательное выполнение: После COMMIT или ROLLBACK первой транзакции блокировка снимается. Вторая транзакция продолжает работу, обновляя уже новое состояние строки.

Пример сценария (PostgreSQL, уровень по умолчанию — READ COMMITTED):

-- Сессия 1
BEGIN;
UPDATE users SET balance = balance - 100 WHERE id = 1; -- Устанавливает X-lock на строку id=1

-- Сессия 2 (запускается одновременно)
BEGIN;
UPDATE users SET balance = balance + 200 WHERE id = 1; -- Эта команда БЛОКИРУЕТСЯ и ждет

-- Сессия 1
COMMIT; -- Снимает блокировку

-- Сессия 2 автоматически продолжает выполнение, но обновляет УЖЕ ИЗМЕНЕННУЮ строку.
-- Финансовый результат зависит от логики приложения.
COMMIT;

Почему это важно: Без блокировок возникла бы потеря обновления (lost update). Для предотвращения проблем в высококонкурентных сценариях используют:

  • Оптимистичные блокировки (версионирование, WHERE с проверкой старого значения).
  • Явные пессимистичные блокировки (например, SELECT ... FOR UPDATE).
  • Правильный выбор уровня изоляции транзакций.