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