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