Оценка проблем конкурентного доступа к пользовательскому балансу

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.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее