Как правильно выполнить сравнение?

Привет. Сия дилемма такова.
Имеется БД, а в ней два столбца: balance и blocked_balance.
balance - обычный счет.
blocked_balance - это счет, который был пополнен клиентом, средства с этого счета нельзя выводить, в отличие от balance.
5eb676a97b8f0600715117.png

Имеется функция, которая считает.
spoiler
function setUserBalance(int $user_id, int $sum) : void {
    $db = DB::getAll('SELECT `balance`, `blocked_balance` FROM `users` WHERE `user_id`=?', [$user_id])[0];
    $balance = (int)$db['balance'];
    $blocked_balance = (int)$db['blocked_balance'];

    if ($sum < 0) {
        if ($blocked_balance > 0 && $blocked_balance >= $sum * -1) {
            DB::exec('UPDATE `users` SET `blocked_balance`=`blocked_balance`-? WHERE `user_id`=?', [$sum * -1, $user_id]);
        } elseif ($blocked_balance > 0 && $blocked_balance < $sum * -1) {
            DB::exec('UPDATE `users` SET `blocked_balance`=0, `balance`=`balance`-? WHERE `user_id`=?', [($sum * -1) - $blocked_balance, $user_id]);
        } else {
            DB::exec('UPDATE `users` SET `balance`=`balance`-? WHERE `user_id`=?', [$sum * -1, $user_id]);
        }
    } else {
        DB::exec('UPDATE `users` SET `balance`=`balance`+? WHERE `user_id`=?', [$sum, $user_id]);
    }
}

Суть функции такова: при списании, если баланс blocked_balance больше или равен чем сумма, то средства списываются с blocked_balance. Если же на blocked_balance не совсем достаточно денег для полного списания, то blocked_balance приравнивается 0, а все вычеты происходят уже в balance и плюс вычитается сумма blocked_balance, которая была аннулирована. Такая вот система.

А сама дилемма в том, что функция не совсем корректная, иногда баланс пользователя уходит в минус, а этого никак не должно быть. Все проверки на баланс перед списанием стоят (есть функция, которая складывает balance + blocked_balance). Поэтому, ошибка именно в этой функции. Я прошу помощи, ибо потратил уже достаточное количество времени, и привел функцию к своему идеалу, но дальше уже не могу, познаний не хватает.

P.S. Использую RedBeanPHP.
  • Вопрос задан
  • 80 просмотров
Решения вопроса 2
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
Посмотрите, как работает ваша функция в случае, когда не хватает средств для заданной суммы на обоих балансах.
Например, balance = 1, blocked_balance = 1, sum = -3. Сработает вторая ветка if, blocked_balance станет равным 0, а balance станет -1.

Кроме того, ваш код не защищён от состояние гонки. Если списание средств будет проходить по двум одновременным запросам одного пользователя (например, с разных браузеров или устройств), то может возникнуть ситуация, когда первый запрос читает балансы, потом второй читает балансы, затем первый изменяет их, а затем второй также изменяет их, но основываясь на старых, ещё не изменённых первым запросом данных. Надо блокировать таблицу на время выполнения функции.
Ответ написан
muxui
@muxui Автор вопроса
Решение оказалось простое: функция setUserBalance() собственно-то и не использовалась там, где должна была.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы