Нужно ли проверять наличие пользователя в базе перед выполнением перевода и почему?
Что проверяют
Понимаете ли вы, что проверка перед действием ничего не гарантирует без транзакции, знаете ли про состояние гонки между проверкой и использованием. Заодно смотрят, где у вас живут инварианты: в коде на Python или в самой базе.
Как отвечать
Проверять нужно, но развести две разные цели. Первая это внятный ответ клиенту: получателя нет, отдаём понятную ошибку, а не трассировку стека. Вторая это целостность денег, и её обеспечивает не if, а база.
Что должно быть в ответе:
- внешние ключи на счета отправителя и получателя, чтобы перевод несуществующему был невозможен физически;
- ограничение на неотрицательный баланс и условное списание, где условие проверяет сама база;
- обе операции в одной транзакции, блокировка строк через
select ... for updateв одинаковом порядке по идентификатору, иначе два встречных перевода дадут взаимную блокировку; - ключ идемпотентности с уникальным индексом, чтобы повтор запроса от клиента не списал дважды.
Условное списание выглядит так, и ноль изменённых строк означает нехватку средств:
update accounts set balance = balance - :amount
where id = :from_id and balance >= :amountГде спотыкаются
Делают отдельный запрос «есть ли пользователь» до транзакции и считают вопрос закрытым. Между проверкой и переводом счёт успевают заблокировать или удалить, это классическая гонка проверки и использования.
Читают баланс в Python, вычитают сумму и записывают результат. Два параллельных перевода прочитают одно и то же значение, и баланс уйдёт в минус. Условие переносится в сам запрос обновления или закрывается блокировкой строки.
Забывают, что ответ тоже утечка. Разные сообщения для «счёта нет» и «перевод запрещён» позволяют перебором узнавать чужие счета, а обёртка всего в общий except превращает бизнес-ошибку в пятисотый код и скрывает причину от поддержки.