Посмотрите, как работает ваша функция в случае, когда не хватает средств для заданной суммы на обоих балансах.
Например, balance = 1, blocked_balance = 1, sum = -3. Сработает вторая ветка if, blocked_balance станет равным 0, а balance станет -1.
Кроме того, ваш код не защищён от состояние гонки. Если списание средств будет проходить по двум одновременным запросам одного пользователя (например, с разных браузеров или устройств), то может возникнуть ситуация, когда первый запрос читает балансы, потом второй читает балансы, затем первый изменяет их, а затем второй также изменяет их, но основываясь на старых, ещё не изменённых первым запросом данных. Надо блокировать таблицу на время выполнения функции.