Кейс: списание бонусов ушло в минус из-за read-then-write без блокировки
Два запроса на списание бонусов пришли почти одновременно. Оба прочитали баланс 500, оба посчитали, что списать 300 допустимо, оба записали 200. По факту списали 600 с баланса, где было 500 — lost update, никакого исключения, деньги просто разошлись.
SELECT без блокировки не защищает от гонки — между read и write окно открыто для любого количества параллельных транзакций. Проблема не в логике списания, она в том, что проверка баланса и его изменение не атомарны как одна операция.
SELECT balance FROM account WHERE id = ? FOR UPDATE;
Спросят про альтернативу без блокировки строки — оптимистичная блокировка через version-колонку и UPDATE ... WHERE version = ?, с retry при конфликте. Дороже логически, но не держит лок на время всей транзакции.
FOR UPDATE — не про синтаксис, а про то, что баланс и его изменение должны считаться одной операцией.
Тренажёр: 600 вопросов, мок с таймером, план повторов
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки