Выбор ответа Senior Django ORM и N+1 спрашивают часто обновлено

Защита от двойного списания

Вопрос

Два параллельных запроса списывают деньги с одного счёта: читают баланс, проверяют, что хватает, и сохраняют новый. Как надёжно предотвратить уход в минус?

Попробуйте сами

Выберите вариант, и откроется разбор.

Показать ответ и разбор

Коротко

Правильный ответ: atomic() + selectforupdate() или условный UPDATE ... WHERE balance = x. Одна транзакция без блокировки не мешает второй транзакции прочитать тот же баланс до коммита первой (в PostgreSQL по умолчанию READ COMMITTED). selectforupdate ставит блокировку на строку, и второй запрос подождёт.

Варианты ответа

  1. atomic() + select_for_update() или условный UPDATE ... WHERE balance >= xправильный ответ
  2. transaction.atomic() без блокировки: транзакция и так изолирует чтение баланса
  3. Проверять баланс дважды подряд перед сохранением, чтобы поймать изменения
  4. Сделать вьюху async def, чтобы запросы выполнялись по очереди в одном цикле

Разбор

Одна транзакция без блокировки не мешает второй транзакции прочитать тот же баланс до коммита первой (в PostgreSQL по умолчанию READ COMMITTED). select_for_update ставит блокировку на строку, и второй запрос подождёт. Альтернатива без явной блокировки: атомарный условный update(balance=F("balance") - x) с фильтром balance__gte=x и проверкой числа обновлённых строк. Двойная проверка и async гонку не убирают.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.