> Как решить проблему гонки при переводе денег между счетами? (Python)
Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы
Компании: HeadHunter
Стек: Python
> Пример ответа
Для решения проблемы гонки (race condition) при переводе денег между счетами в Python необходимо обеспечить атомарность операции. Основные подходы:
- Использование блокировок на уровне БД (рекомендуемый способ):
PYTHONfrom django.db import transaction@transaction.atomicdef transfer_money(from_account_id, to_account_id, amount):# SELECT ... FOR UPDATE блокирует строки до конца транзакцииfrom_account = Account.objects.select_for_update().get(pk=from_account_id)to_account = Account.objects.select_for_update().get(pk=to_account_id)if from_account.balance < amount:raise ValueError("Недостаточно средств")from_account.balance -= amountto_account.balance += amountfrom_account.save()to_account.save()
- Оптимистическая блокировка (через поле version):
PYTHONfrom django.db import transactiondef transfer_money_optimistic(from_account_id, to_account_id, amount):max_retries = 3for attempt in range(max_retries):try:with transaction.atomic():from_account = Account.objects.get(pk=from_account_id)to_account = Account.objects.get(pk=to_account_id)if from_account.balance < amount:raise ValueError("Недостаточно средств")updated = Account.objects.filter(pk=from_account_id,version=from_account.version).update(balance=from_account.balance - amount,version=from_account.version + 1)if updated == 0:raise Account.DoesNotExist # вызовет повторAccount.objects.filter(pk=to_account_id,version=to_account.version).update(balance=to_account.balance + amount,version=to_account.version + 1)breakexcept Account.DoesNotExist:if attempt == max_retries - 1:raisecontinue
- Redis-блокировка для распределенных систем:
PYTHONimport redisimport timer = redis.Redis()def transfer_with_lock(from_id, to_id, amount):lock_key = f"transfer_lock:{min(from_id, to_id)}:{max(from_id, to_id)}"lock = r.lock(lock_key, timeout=10)if lock.acquire(blocking=True, blocking_timeout=5):try:# выполнить переводpassfinally:lock.release()
Ключевые моменты:
- Всегда используйте
SELECT ... FOR UPDATEили эквивалент для пессимистичной блокировки - Проверяйте баланс внутри той же транзакции, где происходит списание
- Избегайте deadlock'ов - блокируйте счета в фиксированном порядке (например, по ID)
- Для высоконагруженных систем рассмотрите шардирование счетов
> Похожие задачи по Python
С какими Python фреймворками кроме Django вы работали?
Как решить проблему race condition на примере системы голосования?
Помогает ли транзакция с уровнем изоляции сериализации при race condition?
Как решить проблему повторного запроса при переводе денег, чтобы избежать двойного списания?
> Похожие задачи по backend
С какими Python фреймворками кроме Django вы работали?
Как решить проблему race condition на примере системы голосования?
Помогает ли транзакция с уровнем изоляции сериализации при race condition?
Как решить проблему повторного запроса при переводе денег, чтобы избежать двойного списания?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью