В UPDATE отсутствует WHERE — он меняет всех пользователей. Проверку достаточности баланса и списание нужно объединить в атомарную операцию.
Корректное списание средств с пользовательского баланса
В UPDATE отсутствует WHERE — он меняет всех пользователей. Проверку достаточности баланса и списание нужно объединить в атомарную операцию.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
В системе есть таблица user, где хранится текущий остаток средств каждого пользователя. Представлена функция на псевдокоде, реализующая процесс снятия суммы с баланса. Необходимо проанализировать её, выявить проблемные места и предложить улучшения, учитывая, что код работает в продакшене. Особое внимание следует уделить вопросам конкурентного доступа, атомарности операций, использованию транзакций и возможным гонкам данных (race conditions).
func withdrawBalance(userID int32, amount float32) {
balance = exec('select balance from user u where u.id=$1', userID)
if balance - amount >= 0.0 {
exec('update user u set balance=balance-$1', amount)
return exec('select balance from user u where u.id=$1', userID)
}
}
Ответ
В UPDATE отсутствует WHERE — он меняет всех пользователей. Проверку достаточности баланса и списание нужно объединить в атомарную операцию.
Деньги храните целыми минимальными единицами, не float32. Проверяйте amount > 0, валюту и допустимый диапазон. Для PostgreSQL базовая операция выглядит так:
UPDATE "user"
SET balance = balance - $2
WHERE id = $1 AND balance >= $2
RETURNING balance;
Ноль возвращённых строк означает, что списание не выполнено: пользователь отсутствует либо денег недостаточно. Предварительный SELECT не защищает от конкурентного списания. Если одновременно пишется журнал операций, используйте транзакцию и уникальный ключ идемпотентности: повтор HTTP-запроса не должен повторно снять деньги. Ошибки БД и результат RETURNING обязательно проверяются.