Защита от двойного списания
Вопрос
Два параллельных запроса списывают деньги с одного счёта: читают баланс, проверяют, что хватает, и сохраняют новый. Как надёжно предотвратить уход в минус?
Попробуйте сами
Выберите вариант, и откроется разбор.
Показать ответ и разбор
Коротко
Варианты ответа
atomic()+select_for_update()или условныйUPDATE ... WHERE balance >= xправильный ответtransaction.atomic()без блокировки: транзакция и так изолирует чтение баланса- Проверять баланс дважды подряд перед сохранением, чтобы поймать изменения
- Сделать вьюху
async def, чтобы запросы выполнялись по очереди в одном цикле
Разбор
Одна транзакция без блокировки не мешает второй транзакции прочитать тот же баланс до коммита первой (в PostgreSQL по умолчанию READ COMMITTED). select_for_update ставит блокировку на строку, и второй запрос подождёт. Альтернатива без явной блокировки: атомарный условный update(balance=F("balance") - x) с фильтром balance__gte=x и проверкой числа обновлённых строк. Двойная проверка и async гонку не убирают.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Предыдущий вопрос
Код-ревью: отчёт по клиентам на Django ORM
Следующий вопрос
select_related против prefetch_related
Ещё по теме «Django ORM и N+1»
- Задвоение сумм при нескольких annotate
- Посчитать SQL-запросы при ленивой загрузке
- Как prefetch_related склеивает данные
- exists() и count() вместо загрузки строк
- Атомарное увеличение счётчика
- Prefetch с вложенным select_related
- transaction.atomic
- Когда выполняется QuerySet