Ответ вслух Middle Транзакции и изоляция спрашивают часто обновлено

Списание с баланса без гонок

Вопрос

Как реализовать списание с баланса пользователя так, чтобы при параллельных запросах баланс не ушёл в минус и списания не потерялись?

Попробуйте сами

Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.

Показать ответ и разбор

Коротко

Главная ошибка: прочитать баланс в приложении, проверить и записать новое значение. Два параллельных запроса оба увидят 100 и оба спишут, одно списание потеряется или баланс уйдёт в минус.

Что должно прозвучать в ответе

  • Проблема: read-modify-write в приложении даёт lost update и уход в минус

  • Атомарный условный UPDATE: SET balance = balance - $1 WHERE id = $2 AND balance >= $1, проверить число затронутых строк

  • Или SELECT ... FOR UPDATE в транзакции, затем проверка и запись

  • Или оптимистичная блокировка через колонку version и повтор

  • CHECK (balance >= 0) как последняя линия защиты

  • Идемпотентность: ключ операции, чтобы повтор запроса не списал дважды

Что могут спросить следом

  • Что выберешь при высокой конкуренции за один счёт?
  • Как хранить историю операций, чтобы баланс можно было пересчитать?

Разбор

Главная ошибка: прочитать баланс в приложении, проверить и записать новое значение. Два параллельных запроса оба увидят 100 и оба спишут, одно списание потеряется или баланс уйдёт в минус. Самое простое и надёжное решение: один атомарный UPDATE с условием, SET balance = balance - $1 WHERE id = $2 AND balance >= $1, и проверка, что затронута одна строка; если ноль, денег не хватило. Если логика сложнее, беру строку под SELECT ... FOR UPDATE в транзакции: конкуренты подождут. При редких конфликтах подходит оптимистичная блокировка с колонкой version и повтором. В базе добавляю CHECK (balance >= 0) как страховку. И отдельно идемпотентность: у операции есть уникальный ключ, повтор того же запроса от клиента не спишет второй раз. В серьёзных системах баланс ещё и выводится из журнала операций, а не только хранится числом.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.

Термины по теме